2018-07-09 03:21:30

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 0/7] mmc: add support for sdhci 4.0

From the SD host controller version 4.0 on, SDHCI implementation either
is version 3 compatible or version 4 mode. This patch-set covers those
changes which are common for SDHCI 4.0 version, regardless of whether
they are used with SD or eMMC storage devices.

This patchset also added a new sdhci driver for Spreadtrum's controller
which supports v4.0 mode.

This patchset has been tested on Spreadtrum's mobile phone, emmc can be
initialized, mounted, read and written, with these changes for common
sdhci framework and sdhci-sprd driver.

Changes from V2:
* Addressed comments from Adrian:
- Added sdhci_enable_v4_mode() for enabling v4 mode instead of determining by reading from registers;
- Added support for 64-bit SDMA address in v4 mode;
- Dropped the changes of ADMA2 data aglinment;
- Added support for "Auto Cmd Auto Select".
* Rebased on v4.18-rc2.
* Dealt with a few issues in sdhci-sprd:
- Save return value of mmc_of_parse();
- Add checking for clk_prepare_enable();
- Use BIT() macro instead.

Changes from v1:
* Addressed comments from Ulf:
- Add dt-bindings for Spreadtrum sdhci;
- Use assigned-clocks* DT bindings to set default source of sdio clock;
- Removed unuseful print;
- Removed two functions which are not used;
- Add back the missing pm_runtime_put_autosuspend() after adding sdhci host.

* Changed Spreadtrum sdhci driver name to sdhci-sprd.

Chunyan Zhang (7):
mmc: sdhci: add sd host v4 mode
mmc: sdhci: made changes for System Address register of SDMA
mmc: sdhci: add ADMA2 64-bit addressing support for V4 mode
mmc: sdhci: add 32-bit block count support for v4 mode
mmc: sdhci: add Auto CMD Auto Select support
mmc: sdhci-sprd: added Spreadtrum's initial host controller
dt-bindings: sdhci-sprd: Add bindings for the sdhci-sprd controller

.../devicetree/bindings/mmc/sdhci-sprd.txt | 41 ++
drivers/mmc/host/Kconfig | 13 +
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/sdhci-sprd.c | 437 +++++++++++++++++++++
drivers/mmc/host/sdhci.c | 107 +++--
drivers/mmc/host/sdhci.h | 22 +-
6 files changed, 596 insertions(+), 25 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-sprd.txt
create mode 100644 drivers/mmc/host/sdhci-sprd.c

--
2.7.4



2018-07-09 03:22:26

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 1/7] mmc: sdhci: add sd host v4 mode

For SD host controller version 4.00 or later ones, there're two
modes of implementation - Version 3.00 compatible mode or
Version 4 mode. This patch introduced an interface to enable
v4 mode.

Signed-off-by: Chunyan Zhang <[email protected]>
---
drivers/mmc/host/sdhci.c | 11 +++++++++++
drivers/mmc/host/sdhci.h | 5 +++++
2 files changed, 16 insertions(+)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 1c828e0..525862f 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -3321,6 +3321,17 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,

EXPORT_SYMBOL_GPL(sdhci_alloc_host);

+/*
+ * Vendor's Host Controller which supports v4 mode can call
+ * this function to enable v4 mode before calling
+ * __sdhci_add_host().
+ */
+void sdhci_enable_v4_mode(struct sdhci_host *host)
+{
+ host->v4_mode = true;
+}
+EXPORT_SYMBOL_GPL(sdhci_enable_v4_mode);
+
static int sdhci_set_dma_mask(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 23966f8..e98249b 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -184,6 +184,7 @@
#define SDHCI_CTRL_DRV_TYPE_D 0x0030
#define SDHCI_CTRL_EXEC_TUNING 0x0040
#define SDHCI_CTRL_TUNED_CLK 0x0080
+#define SDHCI_CTRL_V4_MODE 0x1000
#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000

#define SDHCI_CAPABILITIES 0x40
@@ -566,6 +567,9 @@ struct sdhci_host {
u64 data_timeout;

unsigned long private[0] ____cacheline_aligned;
+
+ /* Host Version 4 Enable */
+ bool v4_mode;
};

struct sdhci_ops {
@@ -747,5 +751,6 @@ bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error,
int *data_error);

void sdhci_dumpregs(struct sdhci_host *host);
+void sdhci_enable_v4_mode(struct sdhci_host *host);

#endif /* __SDHCI_HW_H */
--
2.7.4


2018-07-09 03:22:46

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 2/7] mmc: sdhci: made changes for System Address register of SDMA

According to the SD host controller specification version 4.10, when
Host Version 4 is enabled, SDMA uses ADMA System Address register
(05Fh-058h) instead of using SDMA System Address register to
support both 32-bit and 64-bit addressing.

Signed-off-by: Chunyan Zhang <[email protected]>
---
drivers/mmc/host/sdhci.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 525862f..c7de6a5 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -701,7 +701,7 @@ static void sdhci_adma_table_post(struct sdhci_host *host,
}
}

-static u32 sdhci_sdma_address(struct sdhci_host *host)
+static dma_addr_t sdhci_sdma_address(struct sdhci_host *host)
{
if (host->bounce_buffer)
return host->bounce_addr;
@@ -709,6 +709,18 @@ static u32 sdhci_sdma_address(struct sdhci_host *host)
return sg_dma_address(host->data->sg);
}

+static void sdhci_set_sdma_addr(struct sdhci_host *host, dma_addr_t addr)
+{
+ if (host->v4_mode) {
+ sdhci_writel(host, addr, SDHCI_ADMA_ADDRESS);
+ if (host->flags & SDHCI_USE_64_BIT_DMA)
+ sdhci_writel(host, (u64)addr >> 32, SDHCI_ADMA_ADDRESS_HI);
+ } else {
+ sdhci_writel(host, addr, SDHCI_DMA_ADDRESS);
+ }
+
+}
+
static unsigned int sdhci_target_timeout(struct sdhci_host *host,
struct mmc_command *cmd,
struct mmc_data *data)
@@ -968,8 +980,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
SDHCI_ADMA_ADDRESS_HI);
} else {
WARN_ON(sg_cnt != 1);
- sdhci_writel(host, sdhci_sdma_address(host),
- SDHCI_DMA_ADDRESS);
+ sdhci_set_sdma_addr(host, sdhci_sdma_address(host));
}
}

@@ -2796,7 +2807,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
* some controllers are faulty, don't trust them.
*/
if (intmask & SDHCI_INT_DMA_END) {
- u32 dmastart, dmanow;
+ dma_addr_t dmastart, dmanow;

dmastart = sdhci_sdma_address(host);
dmanow = dmastart + host->data->bytes_xfered;
@@ -2807,9 +2818,9 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
SDHCI_DEFAULT_BOUNDARY_SIZE;
host->data->bytes_xfered = dmanow - dmastart;
- DBG("DMA base 0x%08x, transferred 0x%06x bytes, next 0x%08x\n",
- dmastart, host->data->bytes_xfered, dmanow);
- sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
+ DBG("DMA base 0x%016llx, transferred 0x%06x bytes, next 0x%016llx\n",
+ (u64)dmastart, host->data->bytes_xfered, (u64)dmanow);
+ sdhci_set_sdma_addr(host, dmanow);
}

if (intmask & SDHCI_INT_DATA_END) {
--
2.7.4


2018-07-09 03:22:55

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 3/7] mmc: sdhci: add ADMA2 64-bit addressing support for V4 mode

ADMA2 64-bit addressing support is divided into V3 mode and V4 mode.
So there are two kinds of descriptors for ADMA2 64-bit addressing
i.e. 96-bit Descriptor for V3 mode, and 128-bit Descriptor for V4
mode. 128-bit Descriptor is aligned to 8-byte.

For V4 mode, ADMA2 64-bit addressing is enabled via Host Control 2
register.

Signed-off-by: Chunyan Zhang <[email protected]>
---
drivers/mmc/host/sdhci.c | 28 ++++++++++++++++++++++++----
drivers/mmc/host/sdhci.h | 14 ++++++++++++--
2 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index c7de6a5..7871ae2 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -3486,6 +3486,26 @@ static int sdhci_allocate_bounce_buffer(struct sdhci_host *host)
return 0;
}

+static inline bool sdhci_use_64bit_dma(struct sdhci_host *host)
+{
+ u32 addr64bit_en;
+
+ /*
+ * According to SD Host Controller spec v4.10, bit[27] added from
+ * version 4.10 in Capabilities Register is used as 64-bit System
+ * Address support for V4 mode, 64-bit DMA Addressing for V4 mode
+ * is enabled only if 64-bit Addressing =1 in the Host Control 2
+ * register.
+ */
+ if (host->version == SDHCI_SPEC_410 && host->v4_mode) {
+ addr64bit_en = (sdhci_readw(host, SDHCI_HOST_CONTROL2) &
+ SDHCI_CTRL_64BIT_ADDR);
+ return addr64bit_en && (host->caps & SDHCI_CAN_64BIT_V4);
+ }
+
+ return host->caps & SDHCI_CAN_64BIT;
+}
+
int sdhci_setup_host(struct sdhci_host *host)
{
struct mmc_host *mmc;
@@ -3557,7 +3577,7 @@ int sdhci_setup_host(struct sdhci_host *host)
* SDHCI_QUIRK2_BROKEN_64_BIT_DMA must be left to the drivers to
* implement.
*/
- if (host->caps & SDHCI_CAN_64BIT)
+ if (sdhci_use_64bit_dma(host))
host->flags |= SDHCI_USE_64_BIT_DMA;

if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
@@ -3591,8 +3611,8 @@ int sdhci_setup_host(struct sdhci_host *host)
*/
if (host->flags & SDHCI_USE_64_BIT_DMA) {
host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) *
- SDHCI_ADMA2_64_DESC_SZ;
- host->desc_sz = SDHCI_ADMA2_64_DESC_SZ;
+ SDHCI_ADMA2_64_DESC_SZ(host);
+ host->desc_sz = SDHCI_ADMA2_64_DESC_SZ(host);
} else {
host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) *
SDHCI_ADMA2_32_DESC_SZ;
@@ -3600,7 +3620,7 @@ int sdhci_setup_host(struct sdhci_host *host)
}

host->align_buffer_sz = SDHCI_MAX_SEGS * SDHCI_ADMA2_ALIGN;
- buf = dma_alloc_coherent(mmc_dev(mmc), host->align_buffer_sz +
+ buf = dma_zalloc_coherent(mmc_dev(mmc), host->align_buffer_sz +
host->adma_table_sz, &dma, GFP_KERNEL);
if (!buf) {
pr_warn("%s: Unable to allocate ADMA buffers - falling back to standard DMA\n",
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index e98249b..24fa58a 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -185,6 +185,7 @@
#define SDHCI_CTRL_EXEC_TUNING 0x0040
#define SDHCI_CTRL_TUNED_CLK 0x0080
#define SDHCI_CTRL_V4_MODE 0x1000
+#define SDHCI_CTRL_64BIT_ADDR 0x2000
#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000

#define SDHCI_CAPABILITIES 0x40
@@ -205,6 +206,7 @@
#define SDHCI_CAN_VDD_330 0x01000000
#define SDHCI_CAN_VDD_300 0x02000000
#define SDHCI_CAN_VDD_180 0x04000000
+#define SDHCI_CAN_64BIT_V4 0x08000000
#define SDHCI_CAN_64BIT 0x10000000

#define SDHCI_SUPPORT_SDR50 0x00000001
@@ -271,6 +273,8 @@
#define SDHCI_SPEC_100 0
#define SDHCI_SPEC_200 1
#define SDHCI_SPEC_300 2
+#define SDHCI_SPEC_400 3
+#define SDHCI_SPEC_410 4

/*
* End of controller registers.
@@ -306,8 +310,14 @@ struct sdhci_adma2_32_desc {
*/
#define SDHCI_ADMA2_DESC_ALIGN 8

-/* ADMA2 64-bit DMA descriptor size */
-#define SDHCI_ADMA2_64_DESC_SZ 12
+/*
+ * ADMA2 64-bit DMA descriptor size
+ * According to SD Host Controller spec v4.10, there are two kinds of
+ * descriptors for 64-bit addressing mode: 96-bit Descriptor and 128-bit
+ * Descriptor, if Host Version 4 Enable is set in the Host Control 2
+ * register, 128-bit Descriptor will be selected.
+ */
+#define SDHCI_ADMA2_64_DESC_SZ(host) ((host)->v4_mode ? 16 : 12)

/*
* ADMA2 64-bit descriptor. Note 12-byte descriptor can't always be 8-byte
--
2.7.4


2018-07-09 03:23:13

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 4/7] mmc: sdhci: add 32-bit block count support for v4 mode

When Host Version 4 is enabled, SDMA System Address register is
re-defined as 32-bit Block Count, and SDMA uses ADMA System
Address register (05Fh-058h) instead.

Signed-off-by: Chunyan Zhang <[email protected]>
---
drivers/mmc/host/sdhci.c | 4 +++-
drivers/mmc/host/sdhci.h | 1 +
2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 7871ae2..f64e766 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -889,6 +889,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
{
u8 ctrl;
+ u32 reg;
struct mmc_data *data = cmd->data;

host->data_timeout = 0;
@@ -1021,7 +1022,8 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
/* Set the DMA boundary value and block size */
sdhci_writew(host, SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz),
SDHCI_BLOCK_SIZE);
- sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
+ reg = host->v4_mode ? SDHCI_32BIT_BLK_CNT : SDHCI_BLOCK_COUNT;
+ sdhci_writew(host, data->blocks, reg);
}

static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 24fa58a..889e48b 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -28,6 +28,7 @@

#define SDHCI_DMA_ADDRESS 0x00
#define SDHCI_ARGUMENT2 SDHCI_DMA_ADDRESS
+#define SDHCI_32BIT_BLK_CNT SDHCI_DMA_ADDRESS

#define SDHCI_BLOCK_SIZE 0x04
#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
--
2.7.4


2018-07-09 03:23:40

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 7/7] dt-bindings: sdhci-sprd: Add bindings for the sdhci-sprd controller

From: Chunyan Zhang <[email protected]>

This patch adds the device-tree binding documentation for Spreadtrum
SDHCI driver.

Signed-off-by: Chunyan Zhang <[email protected]>
---
.../devicetree/bindings/mmc/sdhci-sprd.txt | 41 ++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-sprd.txt

diff --git a/Documentation/devicetree/bindings/mmc/sdhci-sprd.txt b/Documentation/devicetree/bindings/mmc/sdhci-sprd.txt
new file mode 100644
index 0000000..45c9978
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/sdhci-sprd.txt
@@ -0,0 +1,41 @@
+* Spreadtrum SDHCI controller (sdhci-sprd)
+
+The Secure Digital (SD) Host controller on Spreadtrum SoCs provides an interface
+for MMC, SD and SDIO types of cards.
+
+This file documents differences between the core properties in mmc.txt
+and the properties used by the sdhci-sprd driver.
+
+Required properties:
+- compatible: Should contain "sprd,sdhci-r11".
+- reg: physical base address of the controller and length.
+- interrupts: Interrupts used by the SDHCI controller.
+- clocks: Should contain phandle for the clock feeding the SDHCI controller
+- clock-names: Should contain the following:
+ "sdio" - SDIO source clock (required)
+ "enable" - gate clock which used for enabling/disabling the device (required)
+
+Optional properties:
+- assigned-clocks: the same with "sdio" clock
+- assigned-clock-parents: the default parent of "sdio" clock
+
+Examples:
+
+sdio0: sdio@20600000 {
+ compatible = "sprd,sdhci-r11";
+ reg = <0 0x20600000 0 0x1000>;
+ interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
+
+ clock-names = "sdio", "enable";
+ clocks = <&ap_clk CLK_EMMC_2X>,
+ <&apahb_gate CLK_EMMC_EB>;
+ assigned-clocks = <&ap_clk CLK_EMMC_2X>;
+ assigned-clock-parents = <&rpll CLK_RPLL_390M>;
+
+ bus-width = <8>;
+ non-removable;
+ no-sdio;
+ no-sd;
+ cap-mmc-hw-reset;
+ status = "okay";
+};
--
2.7.4


2018-07-09 03:24:00

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 5/7] mmc: sdhci: add Auto CMD Auto Select support

As SD Host Controller Specification v4.10 documents:
Host Controller Version 4.10 defines this "Auto CMD Auto Select" mode.
Selection of Auto CMD depends on setting of CMD23 Enable in the Host
Control 2 register which indicates whether card supports CMD23. If CMD23
Enable =1, Auto CMD23 is used and if CMD23 Enable =0, Auto CMD12 is
used. In case of Version 4.10 or later, use of Auto CMD Auto Select is
recommended rather than use of Auto CMD12 Enable or Auto CMD23
Enable.

This patch add this new mode support.

Signed-off-by: Chunyan Zhang <[email protected]>
---
drivers/mmc/host/sdhci.c | 39 ++++++++++++++++++++++++++++-----------
drivers/mmc/host/sdhci.h | 2 ++
2 files changed, 30 insertions(+), 11 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index f64e766..1ca3871 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1033,6 +1033,33 @@ static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
!mrq->cap_cmd_during_tfr;
}

+static inline void sdhci_auto_cmd_enable(struct sdhci_host *host,
+ struct mmc_command *cmd,
+ u16 *mode)
+{
+ /*
+ * In case of Version 4.10 or later, use of 'Auto CMD Auto
+ * Select' is recommended rather than use of 'Auto CMD12
+ * Enable' or 'Auto CMD23 Enable'.
+ */
+ if (host->version >= SDHCI_SPEC_410) {
+ *mode |= SDHCI_TRNS_AUTO_SEL;
+ return;
+ }
+
+ /*
+ * If we are sending CMD23, CMD12 never gets sent
+ * on successful completion (so no Auto-CMD12).
+ */
+ if (sdhci_auto_cmd12(host, cmd->mrq) &&
+ (cmd->opcode != SD_IO_RW_EXTENDED)) {
+ *mode |= SDHCI_TRNS_AUTO_CMD12;
+ } else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
+ *mode |= SDHCI_TRNS_AUTO_CMD23;
+ sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
+ }
+}
+
static void sdhci_set_transfer_mode(struct sdhci_host *host,
struct mmc_command *cmd)
{
@@ -1059,17 +1086,7 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,

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

if (data->flags & MMC_DATA_READ)
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 889e48b..8263ac6 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -42,6 +42,7 @@
#define SDHCI_TRNS_BLK_CNT_EN 0x02
#define SDHCI_TRNS_AUTO_CMD12 0x04
#define SDHCI_TRNS_AUTO_CMD23 0x08
+#define SDHCI_TRNS_AUTO_SEL 0x0C
#define SDHCI_TRNS_READ 0x10
#define SDHCI_TRNS_MULTI 0x20

@@ -185,6 +186,7 @@
#define SDHCI_CTRL_DRV_TYPE_D 0x0030
#define SDHCI_CTRL_EXEC_TUNING 0x0040
#define SDHCI_CTRL_TUNED_CLK 0x0080
+#define SDHCI_CMD23_ENABLE 0x0800
#define SDHCI_CTRL_V4_MODE 0x1000
#define SDHCI_CTRL_64BIT_ADDR 0x2000
#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000
--
2.7.4


2018-07-09 03:24:10

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH V3 6/7] mmc: sdhci-sprd: added Spreadtrum's initial host controller

From: Chunyan Zhang <[email protected]>

This patch adds the initial support of Secure Digital Host Controller
Interface compliant controller found in some latest Spreadtrum chipsets.
This patch has been tested on the version of SPRD-R11 controller.

R11 is a variant based on SD v4.0 specification.

With this driver, R11 mmc can be initialized, can be mounted, read and
written.

Original-by: Billows Wu <[email protected]>
Signed-off-by: Chunyan Zhang <[email protected]>
---
drivers/mmc/host/Kconfig | 13 ++
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/sdhci-sprd.c | 437 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 451 insertions(+)
create mode 100644 drivers/mmc/host/sdhci-sprd.c

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 0581c19..c5424dc 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -581,6 +581,19 @@ config MMC_SDRICOH_CS
To compile this driver as a module, choose M here: the
module will be called sdricoh_cs.

+config MMC_SDHCI_SPRD
+ tristate "Spreadtrum SDIO host Controller"
+ depends on ARCH_SPRD
+ depends on MMC_SDHCI_PLTFM
+ select MMC_SDHCI_IO_ACCESSORS
+ help
+ This selects the SDIO Host Controller in Spreadtrum
+ SoCs, this driver supports R11(IP version: R11P0).
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
config MMC_TMIO_CORE
tristate

diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 85dc132..b0b6802 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o
obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o
+obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
obj-$(CONFIG_MMC_CQHCI) += cqhci.o

ifeq ($(CONFIG_CB710_DEBUG),y)
diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c
new file mode 100644
index 0000000..8d37ecb
--- /dev/null
+++ b/drivers/mmc/host/sdhci-sprd.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Secure Digital Host Controller
+//
+// Copyright (C) 2018 Spreadtrum, Inc.
+// Author: Chunyan Zhang <[email protected]>
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include "sdhci-pltfm.h"
+
+#define SDHCI_SPRD_REG_32_DLL_DLY_OFFSET 0x208
+#define SDHCIBSPRD_IT_WR_DLY_INV BIT(5)
+#define SDHCI_SPRD_BIT_CMD_DLY_INV BIT(13)
+#define SDHCI_SPRD_BIT_POSRD_DLY_INV BIT(21)
+#define SDHCI_SPRD_BIT_NEGRD_DLY_INV BIT(29)
+
+#define SDHCI_SPRD_REG_32_BUSY_POSI 0x250
+#define SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN BIT(25)
+#define SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN BIT(24)
+
+#define SDHCI_SPRD_REG_DEBOUNCE 0x28C
+#define SDHCI_SPRD_BIT_DLL_BAK BIT(0)
+#define SDHCI_SPRD_BIT_DLL_VAL BIT(1)
+
+#define SDHCI_SPRD_INT_SIGNAL_MASK 0x1B7F410B
+
+/* SDHCI_HOST_CONTROL2 */
+#define SDHCI_SPRD_CTRL_HS200 0x0005
+#define SDHCI_SPRD_CTRL_HS400 0x0006
+
+/* SDHCI_SOFTWARE_RESET */
+#define SDHCI_HW_RESET_CARD 0x8 /* For Spreadtrum's design */
+
+#define SDHCI_SPRD_MAX_CUR 0xFFFFFF
+#define SDHCI_SPRD_CLK_MAX_DIV 1023
+
+#define SDHCI_SPRD_CLK_DEF_RATE 26000000
+
+struct sdhci_sprd_host {
+ u32 version;
+ struct clk *clk_sdio;
+ struct clk *clk_enable;
+ u32 base_rate;
+};
+
+#define TO_SPRD_HOST(host) sdhci_pltfm_priv(sdhci_priv(host))
+
+static void sdhci_sprd_init_config(struct sdhci_host *host)
+{
+ u16 val;
+
+ /* set 64-bit addressing modes */
+ val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ val |= SDHCI_CTRL_64BIT_ADDR;
+ sdhci_writew(host, val, SDHCI_HOST_CONTROL2);
+
+ /* set dll backup mode */
+ val = sdhci_readl(host, SDHCI_SPRD_REG_DEBOUNCE);
+ val |= SDHCI_SPRD_BIT_DLL_BAK | SDHCI_SPRD_BIT_DLL_VAL;
+ sdhci_writel(host, val, SDHCI_SPRD_REG_DEBOUNCE);
+
+ /* set CMD23 enabled */
+ val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ val |= SDHCI_CMD23_ENABLE;
+ sdhci_writew(host, val, SDHCI_HOST_CONTROL2);
+}
+
+static inline u32 sdhci_sprd_readl(struct sdhci_host *host, int reg)
+{
+ if (unlikely(reg == SDHCI_MAX_CURRENT))
+ return SDHCI_SPRD_MAX_CUR;
+
+ return readl_relaxed(host->ioaddr + reg);
+}
+
+static inline void sdhci_sprd_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ /* SDHCI_MAX_CURRENT is reserved on Spreadtrum's platform */
+ if (unlikely(reg == SDHCI_MAX_CURRENT))
+ return;
+
+ if (unlikely(reg == SDHCI_SIGNAL_ENABLE || reg == SDHCI_INT_ENABLE))
+ val = val & SDHCI_SPRD_INT_SIGNAL_MASK;
+
+ writel_relaxed(val, host->ioaddr + reg);
+}
+
+static inline void sdhci_sprd_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ if (unlikely(reg == SDHCI_SOFTWARE_RESET)) {
+ if (readb_relaxed(host->ioaddr + reg) & SDHCI_HW_RESET_CARD)
+ val |= SDHCI_HW_RESET_CARD;
+ }
+
+ writeb_relaxed(val, host->ioaddr + reg);
+}
+
+static inline void sdhci_sprd_sd_clk_off(struct sdhci_host *host)
+{
+ u16 ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+
+ ctrl &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, ctrl, SDHCI_CLOCK_CONTROL);
+}
+
+static inline void
+sdhci_sprd_set_dll_invert(struct sdhci_host *host, u32 mask, bool en)
+{
+ u32 dll_dly_offset;
+
+ dll_dly_offset = sdhci_readl(host, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
+ if (en)
+ dll_dly_offset |= mask;
+ else
+ dll_dly_offset &= ~mask;
+ sdhci_writel(host, dll_dly_offset, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
+}
+
+static inline u32 sdhci_sprd_calc_div(u32 base_clk, u32 clk)
+{
+ u32 div;
+
+ /* select 2x clock source */
+ if (base_clk <= clk * 2)
+ return 0;
+
+ div = (u32) (base_clk / (clk * 2));
+
+ if ((base_clk / div) > (clk * 2))
+ div++;
+
+ if (div > SDHCI_SPRD_CLK_MAX_DIV)
+ div = SDHCI_SPRD_CLK_MAX_DIV;
+
+ if (div % 2)
+ div = (div + 1) / 2;
+ else
+ div = div / 2;
+
+ return div;
+}
+
+static inline void _sdhci_sprd_set_clock(struct sdhci_host *host,
+ unsigned int clk)
+{
+ struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
+ u32 div, val, mask;
+
+ div = sdhci_sprd_calc_div(sprd_host->base_rate, clk);
+
+ clk |= ((div & 0x300) >> 2) | ((div & 0xFF) << 8);
+ sdhci_enable_clk(host, clk);
+
+ /* enable auto gate sdhc_enable_auto_gate */
+ val = sdhci_readl(host, SDHCI_SPRD_REG_32_BUSY_POSI);
+ mask = SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN |
+ SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN;
+ if (mask != (val & mask)) {
+ val |= mask;
+ sdhci_writel(host, val, SDHCI_SPRD_REG_32_BUSY_POSI);
+ }
+}
+
+static void sdhci_sprd_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ bool en = false;
+
+ if (clock == 0) {
+ sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
+ } else if (clock != host->clock) {
+ sdhci_sprd_sd_clk_off(host);
+ _sdhci_sprd_set_clock(host, clock);
+
+ if (clock <= 400000)
+ en = true;
+ sdhci_sprd_set_dll_invert(host, SDHCI_SPRD_BIT_CMD_DLY_INV |
+ SDHCI_SPRD_BIT_POSRD_DLY_INV, en);
+ } else {
+ _sdhci_sprd_set_clock(host, clock);
+ }
+}
+
+static unsigned int sdhci_sprd_get_max_clock(struct sdhci_host *host)
+{
+ struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
+
+ return clk_round_rate(sprd_host->clk_sdio, ULONG_MAX);
+}
+
+static unsigned int sdhci_sprd_get_min_clock(struct sdhci_host *host)
+{
+ return 400000;
+}
+
+static void sdhci_sprd_set_uhs_signaling(struct sdhci_host *host,
+ unsigned int timing)
+{
+ u16 ctrl_2;
+
+ if (timing == host->timing)
+ return;
+
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ /* Select Bus Speed Mode for host */
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+ switch (timing) {
+ case MMC_TIMING_UHS_SDR12:
+ ctrl_2 = SDHCI_CTRL_UHS_SDR12;
+ break;
+ case MMC_TIMING_MMC_HS:
+ case MMC_TIMING_SD_HS:
+ case MMC_TIMING_UHS_SDR25:
+ ctrl_2 = SDHCI_CTRL_UHS_SDR25;
+ break;
+ case MMC_TIMING_UHS_SDR50:
+ ctrl_2 = SDHCI_CTRL_UHS_SDR50;
+ break;
+ case MMC_TIMING_UHS_SDR104:
+ ctrl_2 = SDHCI_CTRL_UHS_SDR104;
+ break;
+ case MMC_TIMING_UHS_DDR50:
+ case MMC_TIMING_MMC_DDR52:
+ ctrl_2 = SDHCI_CTRL_UHS_DDR50;
+ break;
+ case MMC_TIMING_MMC_HS200:
+ ctrl_2 = SDHCI_SPRD_CTRL_HS200;
+ break;
+ case MMC_TIMING_MMC_HS400:
+ ctrl_2 = SDHCI_SPRD_CTRL_HS400;
+ break;
+ default:
+ break;
+ }
+
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+static void sdhci_sprd_hw_reset(struct sdhci_host *host)
+{
+ int val;
+
+ /* Note: don't use sdhci_readb/writeb() API here */
+ val = readb_relaxed(host->ioaddr + SDHCI_SOFTWARE_RESET);
+ val &= ~SDHCI_HW_RESET_CARD;
+ writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
+ udelay(10);
+
+ val |= SDHCI_HW_RESET_CARD;
+ writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
+ udelay(300);
+}
+
+static struct sdhci_ops sdhci_sprd_ops = {
+ .read_l = sdhci_sprd_readl,
+ .write_l = sdhci_sprd_writel,
+ .write_b = sdhci_sprd_writeb,
+ .set_clock = sdhci_sprd_set_clock,
+ .get_max_clock = sdhci_sprd_get_max_clock,
+ .get_min_clock = sdhci_sprd_get_min_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .reset = sdhci_reset,
+ .set_uhs_signaling = sdhci_sprd_set_uhs_signaling,
+ .hw_reset = sdhci_sprd_hw_reset,
+};
+
+static const struct sdhci_pltfm_data sdhci_sprd_pdata = {
+ .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK,
+ .quirks2 = SDHCI_QUIRK2_BROKEN_HS200,
+ .ops = &sdhci_sprd_ops,
+};
+
+static int sdhci_sprd_probe(struct platform_device *pdev)
+{
+ struct sdhci_host *host;
+ struct sdhci_sprd_host *sprd_host;
+ struct clk *clk;
+ int ret = 0;
+
+ host = sdhci_pltfm_init(pdev, &sdhci_sprd_pdata, sizeof(*sprd_host));
+ if (IS_ERR(host))
+ return PTR_ERR(host);
+
+ host->dma_mask = DMA_BIT_MASK(64);
+ pdev->dev.dma_mask = &host->dma_mask;
+
+ host->mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
+ MMC_CAP_ERASE | MMC_CAP_CMD23;
+ ret = mmc_of_parse(host->mmc);
+ if (ret)
+ goto pltfm_free;
+
+ sprd_host = TO_SPRD_HOST(host);
+
+ clk = devm_clk_get(&pdev->dev, "sdio");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ goto pltfm_free;
+ }
+ sprd_host->clk_sdio = clk;
+ sprd_host->base_rate = clk_get_rate(sprd_host->clk_sdio);
+ if (!sprd_host->base_rate)
+ sprd_host->base_rate = SDHCI_SPRD_CLK_DEF_RATE;
+
+ clk = devm_clk_get(&pdev->dev, "enable");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ goto pltfm_free;
+ }
+ sprd_host->clk_enable = clk;
+
+ ret = clk_prepare_enable(sprd_host->clk_sdio);
+ if (ret)
+ goto pltfm_free;
+
+ clk_prepare_enable(sprd_host->clk_enable);
+ if (ret)
+ goto clk_disable;
+
+ sdhci_sprd_init_config(host);
+ host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
+ sprd_host->version = ((host->version & SDHCI_VENDOR_VER_MASK) >>
+ SDHCI_VENDOR_VER_SHIFT);
+
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_suspend_ignore_children(&pdev->dev, 1);
+
+ sdhci_enable_v4_mode(host);
+ ret = sdhci_add_host(host);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add mmc host: %d\n", ret);
+ goto pm_runtime_disable;
+ }
+
+ pm_runtime_mark_last_busy(&pdev->dev);
+ pm_runtime_put_autosuspend(&pdev->dev);
+
+ return 0;
+
+pm_runtime_disable:
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+
+ clk_disable_unprepare(sprd_host->clk_enable);
+
+clk_disable:
+ clk_disable_unprepare(sprd_host->clk_sdio);
+
+pltfm_free:
+ sdhci_pltfm_free(pdev);
+ return ret;
+}
+
+static int sdhci_sprd_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
+ struct mmc_host *mmc = host->mmc;
+
+ mmc_remove_host(mmc);
+ clk_disable_unprepare(sprd_host->clk_sdio);
+ clk_disable_unprepare(sprd_host->clk_enable);
+
+ mmc_free_host(mmc);
+
+ return 0;
+}
+
+static const struct of_device_id sdhci_sprd_of_match[] = {
+ { .compatible = "sprd,sdhci-r11", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sdhci_sprd_of_match);
+
+#ifdef CONFIG_PM
+static int sdhci_sprd_runtime_suspend(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
+
+ sdhci_runtime_suspend_host(host);
+
+ clk_disable_unprepare(sprd_host->clk_sdio);
+ clk_disable_unprepare(sprd_host->clk_enable);
+
+ return 0;
+}
+
+static int sdhci_sprd_runtime_resume(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
+
+ clk_prepare_enable(sprd_host->clk_enable);
+ clk_prepare_enable(sprd_host->clk_sdio);
+
+ sdhci_runtime_resume_host(host);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops sdhci_sprd_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(sdhci_sprd_runtime_suspend,
+ sdhci_sprd_runtime_resume, NULL)
+};
+
+static struct platform_driver sdhci_sprd_driver = {
+ .probe = sdhci_sprd_probe,
+ .remove = sdhci_sprd_remove,
+ .driver = {
+ .name = "sdhci_sprd_r11",
+ .of_match_table = of_match_ptr(sdhci_sprd_of_match),
+ .pm = &sdhci_sprd_pm_ops,
+ },
+};
+module_platform_driver(sdhci_sprd_driver);
+
+MODULE_DESCRIPTION("Spreadtrum sdio host controller r11 driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sdhci-sprd-r11");
--
2.7.4


2018-07-16 13:05:55

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 2/7] mmc: sdhci: made changes for System Address register of SDMA

On 09/07/18 06:19, Chunyan Zhang wrote:
> According to the SD host controller specification version 4.10, when
> Host Version 4 is enabled, SDMA uses ADMA System Address register
> (05Fh-058h) instead of using SDMA System Address register to
> support both 32-bit and 64-bit addressing.

The commit message is good but the subject is not so good. What about
"Change SDMA address register for V4 mode"

>
> Signed-off-by: Chunyan Zhang <[email protected]>
> ---
> drivers/mmc/host/sdhci.c | 25 ++++++++++++++++++-------
> 1 file changed, 18 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 525862f..c7de6a5 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -701,7 +701,7 @@ static void sdhci_adma_table_post(struct sdhci_host *host,
> }
> }
>
> -static u32 sdhci_sdma_address(struct sdhci_host *host)
> +static dma_addr_t sdhci_sdma_address(struct sdhci_host *host)
> {
> if (host->bounce_buffer)
> return host->bounce_addr;
> @@ -709,6 +709,18 @@ static u32 sdhci_sdma_address(struct sdhci_host *host)
> return sg_dma_address(host->data->sg);
> }
>
> +static void sdhci_set_sdma_addr(struct sdhci_host *host, dma_addr_t addr)
> +{
> + if (host->v4_mode) {
> + sdhci_writel(host, addr, SDHCI_ADMA_ADDRESS);
> + if (host->flags & SDHCI_USE_64_BIT_DMA)
> + sdhci_writel(host, (u64)addr >> 32, SDHCI_ADMA_ADDRESS_HI);
> + } else {
> + sdhci_writel(host, addr, SDHCI_DMA_ADDRESS);
> + }
> +
> +}
> +
> static unsigned int sdhci_target_timeout(struct sdhci_host *host,
> struct mmc_command *cmd,
> struct mmc_data *data)
> @@ -968,8 +980,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> SDHCI_ADMA_ADDRESS_HI);
> } else {
> WARN_ON(sg_cnt != 1);
> - sdhci_writel(host, sdhci_sdma_address(host),
> - SDHCI_DMA_ADDRESS);
> + sdhci_set_sdma_addr(host, sdhci_sdma_address(host));
> }
> }
>
> @@ -2796,7 +2807,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
> * some controllers are faulty, don't trust them.
> */
> if (intmask & SDHCI_INT_DMA_END) {
> - u32 dmastart, dmanow;
> + dma_addr_t dmastart, dmanow;
>
> dmastart = sdhci_sdma_address(host);
> dmanow = dmastart + host->data->bytes_xfered;
> @@ -2807,9 +2818,9 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
> ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +

That isn't going to work with 64-bit addresses. Need to cast
SDHCI_DEFAULT_BOUNDARY_SIZE to dma_addr_t

> SDHCI_DEFAULT_BOUNDARY_SIZE;
> host->data->bytes_xfered = dmanow - dmastart;
> - DBG("DMA base 0x%08x, transferred 0x%06x bytes, next 0x%08x\n",
> - dmastart, host->data->bytes_xfered, dmanow);
> - sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
> + DBG("DMA base 0x%016llx, transferred 0x%06x bytes, next 0x%016llx\n",

Can you use %pad for dma_addr_t

> + (u64)dmastart, host->data->bytes_xfered, (u64)dmanow);
> + sdhci_set_sdma_addr(host, dmanow);
> }
>
> if (intmask & SDHCI_INT_DATA_END) {
>


2018-07-16 13:26:23

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 2/7] mmc: sdhci: made changes for System Address register of SDMA

On 16/07/18 16:03, Adrian Hunter wrote:
> On 09/07/18 06:19, Chunyan Zhang wrote:
>> According to the SD host controller specification version 4.10, when
>> Host Version 4 is enabled, SDMA uses ADMA System Address register
>> (05Fh-058h) instead of using SDMA System Address register to
>> support both 32-bit and 64-bit addressing.
>
> The commit message is good but the subject is not so good. What about
> "Change SDMA address register for V4 mode"
>
>>
>> Signed-off-by: Chunyan Zhang <[email protected]>
>> ---
>> drivers/mmc/host/sdhci.c | 25 ++++++++++++++++++-------
>> 1 file changed, 18 insertions(+), 7 deletions(-)
>>
>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>> index 525862f..c7de6a5 100644
>> --- a/drivers/mmc/host/sdhci.c
>> +++ b/drivers/mmc/host/sdhci.c
>> @@ -701,7 +701,7 @@ static void sdhci_adma_table_post(struct sdhci_host *host,
>> }
>> }
>>
>> -static u32 sdhci_sdma_address(struct sdhci_host *host)
>> +static dma_addr_t sdhci_sdma_address(struct sdhci_host *host)
>> {
>> if (host->bounce_buffer)
>> return host->bounce_addr;
>> @@ -709,6 +709,18 @@ static u32 sdhci_sdma_address(struct sdhci_host *host)
>> return sg_dma_address(host->data->sg);
>> }
>>
>> +static void sdhci_set_sdma_addr(struct sdhci_host *host, dma_addr_t addr)
>> +{
>> + if (host->v4_mode) {
>> + sdhci_writel(host, addr, SDHCI_ADMA_ADDRESS);
>> + if (host->flags & SDHCI_USE_64_BIT_DMA)
>> + sdhci_writel(host, (u64)addr >> 32, SDHCI_ADMA_ADDRESS_HI);

Also need to adjust the initialization in sdhci_setup_host() which prevents
SDMA with 64-bit DMA i.e.

/* SDMA does not support 64-bit DMA */
if (host->flags & SDHCI_USE_64_BIT_DMA)
host->flags &= ~SDHCI_USE_SDMA;


>> + } else {
>> + sdhci_writel(host, addr, SDHCI_DMA_ADDRESS);
>> + }
>> +
>> +}
>> +
>> static unsigned int sdhci_target_timeout(struct sdhci_host *host,
>> struct mmc_command *cmd,
>> struct mmc_data *data)
>> @@ -968,8 +980,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
>> SDHCI_ADMA_ADDRESS_HI);
>> } else {
>> WARN_ON(sg_cnt != 1);
>> - sdhci_writel(host, sdhci_sdma_address(host),
>> - SDHCI_DMA_ADDRESS);
>> + sdhci_set_sdma_addr(host, sdhci_sdma_address(host));
>> }
>> }
>>
>> @@ -2796,7 +2807,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
>> * some controllers are faulty, don't trust them.
>> */
>> if (intmask & SDHCI_INT_DMA_END) {
>> - u32 dmastart, dmanow;
>> + dma_addr_t dmastart, dmanow;
>>
>> dmastart = sdhci_sdma_address(host);
>> dmanow = dmastart + host->data->bytes_xfered;
>> @@ -2807,9 +2818,9 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
>> ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
>
> That isn't going to work with 64-bit addresses. Need to cast
> SDHCI_DEFAULT_BOUNDARY_SIZE to dma_addr_t
>
>> SDHCI_DEFAULT_BOUNDARY_SIZE;
>> host->data->bytes_xfered = dmanow - dmastart;
>> - DBG("DMA base 0x%08x, transferred 0x%06x bytes, next 0x%08x\n",
>> - dmastart, host->data->bytes_xfered, dmanow);
>> - sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
>> + DBG("DMA base 0x%016llx, transferred 0x%06x bytes, next 0x%016llx\n",
>
> Can you use %pad for dma_addr_t
>
>> + (u64)dmastart, host->data->bytes_xfered, (u64)dmanow);
>> + sdhci_set_sdma_addr(host, dmanow);
>> }
>>
>> if (intmask & SDHCI_INT_DATA_END) {
>>
>
>


2018-07-16 13:37:45

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 3/7] mmc: sdhci: add ADMA2 64-bit addressing support for V4 mode

On 09/07/18 06:19, Chunyan Zhang wrote:
> ADMA2 64-bit addressing support is divided into V3 mode and V4 mode.
> So there are two kinds of descriptors for ADMA2 64-bit addressing
> i.e. 96-bit Descriptor for V3 mode, and 128-bit Descriptor for V4
> mode. 128-bit Descriptor is aligned to 8-byte.
>
> For V4 mode, ADMA2 64-bit addressing is enabled via Host Control 2
> register.
>
> Signed-off-by: Chunyan Zhang <[email protected]>
> ---
> drivers/mmc/host/sdhci.c | 28 ++++++++++++++++++++++++----
> drivers/mmc/host/sdhci.h | 14 ++++++++++++--
> 2 files changed, 36 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index c7de6a5..7871ae2 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -3486,6 +3486,26 @@ static int sdhci_allocate_bounce_buffer(struct sdhci_host *host)
> return 0;
> }
>
> +static inline bool sdhci_use_64bit_dma(struct sdhci_host *host)

I would prefer to call this sdhci_can_64bit_dma()

> +{
> + u32 addr64bit_en;
> +
> + /*
> + * According to SD Host Controller spec v4.10, bit[27] added from
> + * version 4.10 in Capabilities Register is used as 64-bit System
> + * Address support for V4 mode, 64-bit DMA Addressing for V4 mode
> + * is enabled only if 64-bit Addressing =1 in the Host Control 2
> + * register.
> + */
> + if (host->version == SDHCI_SPEC_410 && host->v4_mode) {

That should be >= not ==

> + addr64bit_en = (sdhci_readw(host, SDHCI_HOST_CONTROL2) &
> + SDHCI_CTRL_64BIT_ADDR);

This seems the wrong way around. We should write SDHCI_CTRL_64BIT_ADDR when
we decide to use 64-bit DMA in V4 mode.

> + return addr64bit_en && (host->caps & SDHCI_CAN_64BIT_V4);
> + }
> +
> + return host->caps & SDHCI_CAN_64BIT;
> +}
> +
> int sdhci_setup_host(struct sdhci_host *host)
> {
> struct mmc_host *mmc;
> @@ -3557,7 +3577,7 @@ int sdhci_setup_host(struct sdhci_host *host)
> * SDHCI_QUIRK2_BROKEN_64_BIT_DMA must be left to the drivers to
> * implement.
> */
> - if (host->caps & SDHCI_CAN_64BIT)
> + if (sdhci_use_64bit_dma(host))
> host->flags |= SDHCI_USE_64_BIT_DMA;
>
> if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
> @@ -3591,8 +3611,8 @@ int sdhci_setup_host(struct sdhci_host *host)
> */
> if (host->flags & SDHCI_USE_64_BIT_DMA) {
> host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) *
> - SDHCI_ADMA2_64_DESC_SZ;
> - host->desc_sz = SDHCI_ADMA2_64_DESC_SZ;
> + SDHCI_ADMA2_64_DESC_SZ(host);
> + host->desc_sz = SDHCI_ADMA2_64_DESC_SZ(host);
> } else {
> host->adma_table_sz = (SDHCI_MAX_SEGS * 2 + 1) *
> SDHCI_ADMA2_32_DESC_SZ;
> @@ -3600,7 +3620,7 @@ int sdhci_setup_host(struct sdhci_host *host)
> }
>
> host->align_buffer_sz = SDHCI_MAX_SEGS * SDHCI_ADMA2_ALIGN;
> - buf = dma_alloc_coherent(mmc_dev(mmc), host->align_buffer_sz +
> + buf = dma_zalloc_coherent(mmc_dev(mmc), host->align_buffer_sz +

The reason for the zalloc could use a comment

> host->adma_table_sz, &dma, GFP_KERNEL);
> if (!buf) {
> pr_warn("%s: Unable to allocate ADMA buffers - falling back to standard DMA\n",
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index e98249b..24fa58a 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -185,6 +185,7 @@
> #define SDHCI_CTRL_EXEC_TUNING 0x0040
> #define SDHCI_CTRL_TUNED_CLK 0x0080
> #define SDHCI_CTRL_V4_MODE 0x1000
> +#define SDHCI_CTRL_64BIT_ADDR 0x2000
> #define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000
>
> #define SDHCI_CAPABILITIES 0x40
> @@ -205,6 +206,7 @@
> #define SDHCI_CAN_VDD_330 0x01000000
> #define SDHCI_CAN_VDD_300 0x02000000
> #define SDHCI_CAN_VDD_180 0x04000000
> +#define SDHCI_CAN_64BIT_V4 0x08000000
> #define SDHCI_CAN_64BIT 0x10000000
>
> #define SDHCI_SUPPORT_SDR50 0x00000001
> @@ -271,6 +273,8 @@
> #define SDHCI_SPEC_100 0
> #define SDHCI_SPEC_200 1
> #define SDHCI_SPEC_300 2
> +#define SDHCI_SPEC_400 3
> +#define SDHCI_SPEC_410 4

Let's also add

#define SDHCI_SPEC_420 5

>
> /*
> * End of controller registers.
> @@ -306,8 +310,14 @@ struct sdhci_adma2_32_desc {
> */
> #define SDHCI_ADMA2_DESC_ALIGN 8
>
> -/* ADMA2 64-bit DMA descriptor size */
> -#define SDHCI_ADMA2_64_DESC_SZ 12
> +/*
> + * ADMA2 64-bit DMA descriptor size
> + * According to SD Host Controller spec v4.10, there are two kinds of
> + * descriptors for 64-bit addressing mode: 96-bit Descriptor and 128-bit
> + * Descriptor, if Host Version 4 Enable is set in the Host Control 2
> + * register, 128-bit Descriptor will be selected.
> + */
> +#define SDHCI_ADMA2_64_DESC_SZ(host) ((host)->v4_mode ? 16 : 12)
>
> /*
> * ADMA2 64-bit descriptor. Note 12-byte descriptor can't always be 8-byte
>


2018-07-17 08:32:18

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 4/7] mmc: sdhci: add 32-bit block count support for v4 mode

On 09/07/18 06:19, Chunyan Zhang wrote:
> When Host Version 4 is enabled, SDMA System Address register is
> re-defined as 32-bit Block Count, and SDMA uses ADMA System
> Address register (05Fh-058h) instead.
>
> Signed-off-by: Chunyan Zhang <[email protected]>
> ---
> drivers/mmc/host/sdhci.c | 4 +++-
> drivers/mmc/host/sdhci.h | 1 +
> 2 files changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 7871ae2..f64e766 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -889,6 +889,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
> static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> {
> u8 ctrl;
> + u32 reg;
> struct mmc_data *data = cmd->data;
>
> host->data_timeout = 0;
> @@ -1021,7 +1022,8 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> /* Set the DMA boundary value and block size */
> sdhci_writew(host, SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz),
> SDHCI_BLOCK_SIZE);
> - sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
> + reg = host->v4_mode ? SDHCI_32BIT_BLK_CNT : SDHCI_BLOCK_COUNT;
> + sdhci_writew(host, data->blocks, reg);

The specification says to set 16-bit block count register to zero when using
32-bit block count. It also says it is valid for V4.1 onwards and also for
V4 with SDMA and auto-CMD23.

So maybe we should continue to use the 16-bit block count register with V4.0

> }
>
> static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index 24fa58a..889e48b 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -28,6 +28,7 @@
>
> #define SDHCI_DMA_ADDRESS 0x00
> #define SDHCI_ARGUMENT2 SDHCI_DMA_ADDRESS
> +#define SDHCI_32BIT_BLK_CNT SDHCI_DMA_ADDRESS
>
> #define SDHCI_BLOCK_SIZE 0x04
> #define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
>


2018-07-17 09:16:34

by Chunyan Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 4/7] mmc: sdhci: add 32-bit block count support for v4 mode

Hi,

On 17 July 2018 at 16:29, Adrian Hunter <[email protected]> wrote:
> On 09/07/18 06:19, Chunyan Zhang wrote:
>> When Host Version 4 is enabled, SDMA System Address register is
>> re-defined as 32-bit Block Count, and SDMA uses ADMA System
>> Address register (05Fh-058h) instead.
>>
>> Signed-off-by: Chunyan Zhang <[email protected]>
>> ---
>> drivers/mmc/host/sdhci.c | 4 +++-
>> drivers/mmc/host/sdhci.h | 1 +
>> 2 files changed, 4 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>> index 7871ae2..f64e766 100644
>> --- a/drivers/mmc/host/sdhci.c
>> +++ b/drivers/mmc/host/sdhci.c
>> @@ -889,6 +889,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
>> static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
>> {
>> u8 ctrl;
>> + u32 reg;
>> struct mmc_data *data = cmd->data;
>>
>> host->data_timeout = 0;
>> @@ -1021,7 +1022,8 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
>> /* Set the DMA boundary value and block size */
>> sdhci_writew(host, SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz),
>> SDHCI_BLOCK_SIZE);
>> - sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
>> + reg = host->v4_mode ? SDHCI_32BIT_BLK_CNT : SDHCI_BLOCK_COUNT;
>> + sdhci_writew(host, data->blocks, reg);
>
> The specification says to set 16-bit block count register to zero when using
> 32-bit block count. It also says it is valid for V4.1 onwards and also for
> V4 with SDMA and auto-CMD23.
>
> So maybe we should continue to use the 16-bit block count register with V4.0

Ok.

Where can I get a V4.0 specification? I only have V4.10 on hands.

>
>> }
>>
>> static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
>> index 24fa58a..889e48b 100644
>> --- a/drivers/mmc/host/sdhci.h
>> +++ b/drivers/mmc/host/sdhci.h
>> @@ -28,6 +28,7 @@
>>
>> #define SDHCI_DMA_ADDRESS 0x00
>> #define SDHCI_ARGUMENT2 SDHCI_DMA_ADDRESS
>> +#define SDHCI_32BIT_BLK_CNT SDHCI_DMA_ADDRESS
>>
>> #define SDHCI_BLOCK_SIZE 0x04
>> #define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
>>
>

2018-07-17 10:38:56

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 4/7] mmc: sdhci: add 32-bit block count support for v4 mode

On 17/07/18 12:14, Chunyan Zhang wrote:
> Hi,
>
> On 17 July 2018 at 16:29, Adrian Hunter <[email protected]> wrote:
>> On 09/07/18 06:19, Chunyan Zhang wrote:
>>> When Host Version 4 is enabled, SDMA System Address register is
>>> re-defined as 32-bit Block Count, and SDMA uses ADMA System
>>> Address register (05Fh-058h) instead.
>>>
>>> Signed-off-by: Chunyan Zhang <[email protected]>
>>> ---
>>> drivers/mmc/host/sdhci.c | 4 +++-
>>> drivers/mmc/host/sdhci.h | 1 +
>>> 2 files changed, 4 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>>> index 7871ae2..f64e766 100644
>>> --- a/drivers/mmc/host/sdhci.c
>>> +++ b/drivers/mmc/host/sdhci.c
>>> @@ -889,6 +889,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
>>> static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
>>> {
>>> u8 ctrl;
>>> + u32 reg;
>>> struct mmc_data *data = cmd->data;
>>>
>>> host->data_timeout = 0;
>>> @@ -1021,7 +1022,8 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
>>> /* Set the DMA boundary value and block size */
>>> sdhci_writew(host, SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz),
>>> SDHCI_BLOCK_SIZE);
>>> - sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
>>> + reg = host->v4_mode ? SDHCI_32BIT_BLK_CNT : SDHCI_BLOCK_COUNT;
>>> + sdhci_writew(host, data->blocks, reg);
>>
>> The specification says to set 16-bit block count register to zero when using
>> 32-bit block count. It also says it is valid for V4.1 onwards and also for
>> V4 with SDMA and auto-CMD23.
>>
>> So maybe we should continue to use the 16-bit block count register with V4.0
>
> Ok.
>
> Where can I get a V4.0 specification? I only have V4.10 on hands.

I do not have a copy I can share, but the 4.1 specification already
indicates what is V4.1 (onwards) and what only requires Host Version 4 Enable.

>
>>
>>> }
>>>
>>> static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
>>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
>>> index 24fa58a..889e48b 100644
>>> --- a/drivers/mmc/host/sdhci.h
>>> +++ b/drivers/mmc/host/sdhci.h
>>> @@ -28,6 +28,7 @@
>>>
>>> #define SDHCI_DMA_ADDRESS 0x00
>>> #define SDHCI_ARGUMENT2 SDHCI_DMA_ADDRESS
>>> +#define SDHCI_32BIT_BLK_CNT SDHCI_DMA_ADDRESS
>>>
>>> #define SDHCI_BLOCK_SIZE 0x04
>>> #define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
>>>
>>
>


2018-07-17 11:19:16

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 5/7] mmc: sdhci: add Auto CMD Auto Select support

On 09/07/18 06:19, Chunyan Zhang wrote:
> As SD Host Controller Specification v4.10 documents:
> Host Controller Version 4.10 defines this "Auto CMD Auto Select" mode.
> Selection of Auto CMD depends on setting of CMD23 Enable in the Host
> Control 2 register which indicates whether card supports CMD23. If CMD23
> Enable =1, Auto CMD23 is used and if CMD23 Enable =0, Auto CMD12 is
> used. In case of Version 4.10 or later, use of Auto CMD Auto Select is
> recommended rather than use of Auto CMD12 Enable or Auto CMD23
> Enable.
>
> This patch add this new mode support.
>
> Signed-off-by: Chunyan Zhang <[email protected]>
> ---
> drivers/mmc/host/sdhci.c | 39 ++++++++++++++++++++++++++++-----------
> drivers/mmc/host/sdhci.h | 2 ++
> 2 files changed, 30 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index f64e766..1ca3871 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -1033,6 +1033,33 @@ static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
> !mrq->cap_cmd_during_tfr;
> }
>
> +static inline void sdhci_auto_cmd_enable(struct sdhci_host *host,
> + struct mmc_command *cmd,
> + u16 *mode)
> +{
> + /*
> + * In case of Version 4.10 or later, use of 'Auto CMD Auto
> + * Select' is recommended rather than use of 'Auto CMD12
> + * Enable' or 'Auto CMD23 Enable'.
> + */
> + if (host->version >= SDHCI_SPEC_410) {
> + *mode |= SDHCI_TRNS_AUTO_SEL;

The rules for selecting auto commands still need to be applied. In effect:

if (sdhci_auto_cmd12(host, cmd->mrq) &&
(cmd->opcode != SD_IO_RW_EXTENDED)) {
*mode |= SDHCI_TRNS_AUTO_SEL;
} else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
*mode |= SDHCI_TRNS_AUTO_SEL;
sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
}

Also the selection of Host Control 2 register CMD23 Enable should be done by
SDHCI (if supported by the card) not the Spreadtrum driver.

> + return;
> + }
> +
> + /*
> + * If we are sending CMD23, CMD12 never gets sent
> + * on successful completion (so no Auto-CMD12).
> + */
> + if (sdhci_auto_cmd12(host, cmd->mrq) &&
> + (cmd->opcode != SD_IO_RW_EXTENDED)) {
> + *mode |= SDHCI_TRNS_AUTO_CMD12;
> + } else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
> + *mode |= SDHCI_TRNS_AUTO_CMD23;
> + sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
> + }
> +}
> +
> static void sdhci_set_transfer_mode(struct sdhci_host *host,
> struct mmc_command *cmd)
> {
> @@ -1059,17 +1086,7 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
>
> if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
> mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
> - /*
> - * If we are sending CMD23, CMD12 never gets sent
> - * on successful completion (so no Auto-CMD12).
> - */
> - if (sdhci_auto_cmd12(host, cmd->mrq) &&
> - (cmd->opcode != SD_IO_RW_EXTENDED))
> - mode |= SDHCI_TRNS_AUTO_CMD12;
> - else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
> - mode |= SDHCI_TRNS_AUTO_CMD23;
> - sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
> - }
> + sdhci_auto_cmd_enable(host, cmd, &mode);
> }
>
> if (data->flags & MMC_DATA_READ)
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index 889e48b..8263ac6 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -42,6 +42,7 @@
> #define SDHCI_TRNS_BLK_CNT_EN 0x02
> #define SDHCI_TRNS_AUTO_CMD12 0x04
> #define SDHCI_TRNS_AUTO_CMD23 0x08
> +#define SDHCI_TRNS_AUTO_SEL 0x0C
> #define SDHCI_TRNS_READ 0x10
> #define SDHCI_TRNS_MULTI 0x20
>
> @@ -185,6 +186,7 @@
> #define SDHCI_CTRL_DRV_TYPE_D 0x0030
> #define SDHCI_CTRL_EXEC_TUNING 0x0040
> #define SDHCI_CTRL_TUNED_CLK 0x0080
> +#define SDHCI_CMD23_ENABLE 0x0800
> #define SDHCI_CTRL_V4_MODE 0x1000
> #define SDHCI_CTRL_64BIT_ADDR 0x2000
> #define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000
>


2018-07-17 11:55:09

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 6/7] mmc: sdhci-sprd: added Spreadtrum's initial host controller

On 09/07/18 06:19, Chunyan Zhang wrote:
> From: Chunyan Zhang <[email protected]>
>
> This patch adds the initial support of Secure Digital Host Controller
> Interface compliant controller found in some latest Spreadtrum chipsets.
> This patch has been tested on the version of SPRD-R11 controller.
>
> R11 is a variant based on SD v4.0 specification.
>
> With this driver, R11 mmc can be initialized, can be mounted, read and
> written.
>
> Original-by: Billows Wu <[email protected]>
> Signed-off-by: Chunyan Zhang <[email protected]>
> ---
> drivers/mmc/host/Kconfig | 13 ++
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/sdhci-sprd.c | 437 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 451 insertions(+)
> create mode 100644 drivers/mmc/host/sdhci-sprd.c
>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 0581c19..c5424dc 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -581,6 +581,19 @@ config MMC_SDRICOH_CS
> To compile this driver as a module, choose M here: the
> module will be called sdricoh_cs.
>
> +config MMC_SDHCI_SPRD
> + tristate "Spreadtrum SDIO host Controller"
> + depends on ARCH_SPRD
> + depends on MMC_SDHCI_PLTFM
> + select MMC_SDHCI_IO_ACCESSORS
> + help
> + This selects the SDIO Host Controller in Spreadtrum
> + SoCs, this driver supports R11(IP version: R11P0).
> +
> + If you have a controller with this interface, say Y or M here.
> +
> + If unsure, say N.
> +
> config MMC_TMIO_CORE
> tristate
>
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 85dc132..b0b6802 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -89,6 +89,7 @@ obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
> obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o
> obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
> obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o
> +obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
> obj-$(CONFIG_MMC_CQHCI) += cqhci.o
>
> ifeq ($(CONFIG_CB710_DEBUG),y)
> diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c
> new file mode 100644
> index 0000000..8d37ecb
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-sprd.c
> @@ -0,0 +1,437 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Secure Digital Host Controller
> +//
> +// Copyright (C) 2018 Spreadtrum, Inc.
> +// Author: Chunyan Zhang <[email protected]>
> +
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/highmem.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +
> +#include "sdhci-pltfm.h"
> +
> +#define SDHCI_SPRD_REG_32_DLL_DLY_OFFSET 0x208
> +#define SDHCIBSPRD_IT_WR_DLY_INV BIT(5)
> +#define SDHCI_SPRD_BIT_CMD_DLY_INV BIT(13)
> +#define SDHCI_SPRD_BIT_POSRD_DLY_INV BIT(21)
> +#define SDHCI_SPRD_BIT_NEGRD_DLY_INV BIT(29)
> +
> +#define SDHCI_SPRD_REG_32_BUSY_POSI 0x250
> +#define SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN BIT(25)
> +#define SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN BIT(24)
> +
> +#define SDHCI_SPRD_REG_DEBOUNCE 0x28C
> +#define SDHCI_SPRD_BIT_DLL_BAK BIT(0)
> +#define SDHCI_SPRD_BIT_DLL_VAL BIT(1)
> +
> +#define SDHCI_SPRD_INT_SIGNAL_MASK 0x1B7F410B
> +
> +/* SDHCI_HOST_CONTROL2 */
> +#define SDHCI_SPRD_CTRL_HS200 0x0005
> +#define SDHCI_SPRD_CTRL_HS400 0x0006
> +
> +/* SDHCI_SOFTWARE_RESET */
> +#define SDHCI_HW_RESET_CARD 0x8 /* For Spreadtrum's design */
> +
> +#define SDHCI_SPRD_MAX_CUR 0xFFFFFF
> +#define SDHCI_SPRD_CLK_MAX_DIV 1023
> +
> +#define SDHCI_SPRD_CLK_DEF_RATE 26000000
> +
> +struct sdhci_sprd_host {
> + u32 version;
> + struct clk *clk_sdio;
> + struct clk *clk_enable;
> + u32 base_rate;
> +};
> +
> +#define TO_SPRD_HOST(host) sdhci_pltfm_priv(sdhci_priv(host))
> +
> +static void sdhci_sprd_init_config(struct sdhci_host *host)
> +{
> + u16 val;
> +
> + /* set 64-bit addressing modes */
> + val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> + val |= SDHCI_CTRL_64BIT_ADDR;
> + sdhci_writew(host, val, SDHCI_HOST_CONTROL2);

SDHCI should do that when enabling 64-bit DMA in V4 mode. Needs to be done
either in sdhci_prepare_data() or after every reset or resume (e.g. when
->enable_dma() is called).

> +
> + /* set dll backup mode */
> + val = sdhci_readl(host, SDHCI_SPRD_REG_DEBOUNCE);
> + val |= SDHCI_SPRD_BIT_DLL_BAK | SDHCI_SPRD_BIT_DLL_VAL;
> + sdhci_writel(host, val, SDHCI_SPRD_REG_DEBOUNCE);
> +
> + /* set CMD23 enabled */
> + val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> + val |= SDHCI_CMD23_ENABLE;
> + sdhci_writew(host, val, SDHCI_HOST_CONTROL2);

SDHCI should do that. Needs to be done either when enabling auto-CMD23 or
after every reset or resume.

> +}
> +
> +static inline u32 sdhci_sprd_readl(struct sdhci_host *host, int reg)
> +{
> + if (unlikely(reg == SDHCI_MAX_CURRENT))
> + return SDHCI_SPRD_MAX_CUR;
> +
> + return readl_relaxed(host->ioaddr + reg);
> +}
> +
> +static inline void sdhci_sprd_writel(struct sdhci_host *host, u32 val, int reg)
> +{
> + /* SDHCI_MAX_CURRENT is reserved on Spreadtrum's platform */
> + if (unlikely(reg == SDHCI_MAX_CURRENT))
> + return;
> +
> + if (unlikely(reg == SDHCI_SIGNAL_ENABLE || reg == SDHCI_INT_ENABLE))
> + val = val & SDHCI_SPRD_INT_SIGNAL_MASK;
> +
> + writel_relaxed(val, host->ioaddr + reg);
> +}
> +
> +static inline void sdhci_sprd_writeb(struct sdhci_host *host, u8 val, int reg)
> +{
> + if (unlikely(reg == SDHCI_SOFTWARE_RESET)) {
> + if (readb_relaxed(host->ioaddr + reg) & SDHCI_HW_RESET_CARD)
> + val |= SDHCI_HW_RESET_CARD;
> + }
> +
> + writeb_relaxed(val, host->ioaddr + reg);
> +}
> +
> +static inline void sdhci_sprd_sd_clk_off(struct sdhci_host *host)
> +{
> + u16 ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
> +
> + ctrl &= ~SDHCI_CLOCK_CARD_EN;
> + sdhci_writew(host, ctrl, SDHCI_CLOCK_CONTROL);
> +}
> +
> +static inline void
> +sdhci_sprd_set_dll_invert(struct sdhci_host *host, u32 mask, bool en)
> +{
> + u32 dll_dly_offset;
> +
> + dll_dly_offset = sdhci_readl(host, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
> + if (en)
> + dll_dly_offset |= mask;
> + else
> + dll_dly_offset &= ~mask;
> + sdhci_writel(host, dll_dly_offset, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
> +}
> +
> +static inline u32 sdhci_sprd_calc_div(u32 base_clk, u32 clk)
> +{
> + u32 div;
> +
> + /* select 2x clock source */
> + if (base_clk <= clk * 2)
> + return 0;
> +
> + div = (u32) (base_clk / (clk * 2));
> +
> + if ((base_clk / div) > (clk * 2))
> + div++;
> +
> + if (div > SDHCI_SPRD_CLK_MAX_DIV)
> + div = SDHCI_SPRD_CLK_MAX_DIV;
> +
> + if (div % 2)
> + div = (div + 1) / 2;
> + else
> + div = div / 2;
> +
> + return div;
> +}
> +
> +static inline void _sdhci_sprd_set_clock(struct sdhci_host *host,
> + unsigned int clk)
> +{
> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
> + u32 div, val, mask;
> +
> + div = sdhci_sprd_calc_div(sprd_host->base_rate, clk);
> +
> + clk |= ((div & 0x300) >> 2) | ((div & 0xFF) << 8);
> + sdhci_enable_clk(host, clk);
> +
> + /* enable auto gate sdhc_enable_auto_gate */
> + val = sdhci_readl(host, SDHCI_SPRD_REG_32_BUSY_POSI);
> + mask = SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN |
> + SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN;
> + if (mask != (val & mask)) {
> + val |= mask;
> + sdhci_writel(host, val, SDHCI_SPRD_REG_32_BUSY_POSI);
> + }
> +}
> +
> +static void sdhci_sprd_set_clock(struct sdhci_host *host, unsigned int clock)
> +{
> + bool en = false;
> +
> + if (clock == 0) {
> + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
> + } else if (clock != host->clock) {
> + sdhci_sprd_sd_clk_off(host);
> + _sdhci_sprd_set_clock(host, clock);
> +
> + if (clock <= 400000)
> + en = true;
> + sdhci_sprd_set_dll_invert(host, SDHCI_SPRD_BIT_CMD_DLY_INV |
> + SDHCI_SPRD_BIT_POSRD_DLY_INV, en);
> + } else {
> + _sdhci_sprd_set_clock(host, clock);
> + }
> +}
> +
> +static unsigned int sdhci_sprd_get_max_clock(struct sdhci_host *host)
> +{
> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
> +
> + return clk_round_rate(sprd_host->clk_sdio, ULONG_MAX);
> +}
> +
> +static unsigned int sdhci_sprd_get_min_clock(struct sdhci_host *host)
> +{
> + return 400000;
> +}
> +
> +static void sdhci_sprd_set_uhs_signaling(struct sdhci_host *host,
> + unsigned int timing)
> +{
> + u16 ctrl_2;
> +
> + if (timing == host->timing)
> + return;
> +
> + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> + /* Select Bus Speed Mode for host */
> + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
> + switch (timing) {
> + case MMC_TIMING_UHS_SDR12:
> + ctrl_2 = SDHCI_CTRL_UHS_SDR12;

Shouldn't this and the following ones be '|=' not '='

> + break;
> + case MMC_TIMING_MMC_HS:
> + case MMC_TIMING_SD_HS:
> + case MMC_TIMING_UHS_SDR25:
> + ctrl_2 = SDHCI_CTRL_UHS_SDR25;
> + break;
> + case MMC_TIMING_UHS_SDR50:
> + ctrl_2 = SDHCI_CTRL_UHS_SDR50;
> + break;
> + case MMC_TIMING_UHS_SDR104:
> + ctrl_2 = SDHCI_CTRL_UHS_SDR104;
> + break;
> + case MMC_TIMING_UHS_DDR50:
> + case MMC_TIMING_MMC_DDR52:
> + ctrl_2 = SDHCI_CTRL_UHS_DDR50;
> + break;
> + case MMC_TIMING_MMC_HS200:
> + ctrl_2 = SDHCI_SPRD_CTRL_HS200;
> + break;
> + case MMC_TIMING_MMC_HS400:
> + ctrl_2 = SDHCI_SPRD_CTRL_HS400;
> + break;
> + default:
> + break;
> + }
> +
> + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +}
> +
> +static void sdhci_sprd_hw_reset(struct sdhci_host *host)
> +{
> + int val;
> +
> + /* Note: don't use sdhci_readb/writeb() API here */
> + val = readb_relaxed(host->ioaddr + SDHCI_SOFTWARE_RESET);
> + val &= ~SDHCI_HW_RESET_CARD;
> + writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
> + udelay(10);
> +
> + val |= SDHCI_HW_RESET_CARD;
> + writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
> + udelay(300);

usleep_range() maybe

> +}
> +
> +static struct sdhci_ops sdhci_sprd_ops = {
> + .read_l = sdhci_sprd_readl,
> + .write_l = sdhci_sprd_writel,
> + .write_b = sdhci_sprd_writeb,
> + .set_clock = sdhci_sprd_set_clock,
> + .get_max_clock = sdhci_sprd_get_max_clock,
> + .get_min_clock = sdhci_sprd_get_min_clock,
> + .set_bus_width = sdhci_set_bus_width,
> + .reset = sdhci_reset,
> + .set_uhs_signaling = sdhci_sprd_set_uhs_signaling,
> + .hw_reset = sdhci_sprd_hw_reset,
> +};
> +
> +static const struct sdhci_pltfm_data sdhci_sprd_pdata = {
> + .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK,
> + .quirks2 = SDHCI_QUIRK2_BROKEN_HS200,
> + .ops = &sdhci_sprd_ops,
> +};
> +
> +static int sdhci_sprd_probe(struct platform_device *pdev)
> +{
> + struct sdhci_host *host;
> + struct sdhci_sprd_host *sprd_host;
> + struct clk *clk;
> + int ret = 0;
> +
> + host = sdhci_pltfm_init(pdev, &sdhci_sprd_pdata, sizeof(*sprd_host));
> + if (IS_ERR(host))
> + return PTR_ERR(host);
> +
> + host->dma_mask = DMA_BIT_MASK(64);
> + pdev->dev.dma_mask = &host->dma_mask;
> +
> + host->mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
> + MMC_CAP_ERASE | MMC_CAP_CMD23;
> + ret = mmc_of_parse(host->mmc);
> + if (ret)
> + goto pltfm_free;
> +
> + sprd_host = TO_SPRD_HOST(host);
> +
> + clk = devm_clk_get(&pdev->dev, "sdio");
> + if (IS_ERR(clk)) {
> + ret = PTR_ERR(clk);
> + goto pltfm_free;
> + }
> + sprd_host->clk_sdio = clk;
> + sprd_host->base_rate = clk_get_rate(sprd_host->clk_sdio);
> + if (!sprd_host->base_rate)
> + sprd_host->base_rate = SDHCI_SPRD_CLK_DEF_RATE;
> +
> + clk = devm_clk_get(&pdev->dev, "enable");
> + if (IS_ERR(clk)) {
> + ret = PTR_ERR(clk);
> + goto pltfm_free;
> + }
> + sprd_host->clk_enable = clk;
> +
> + ret = clk_prepare_enable(sprd_host->clk_sdio);
> + if (ret)
> + goto pltfm_free;
> +
> + clk_prepare_enable(sprd_host->clk_enable);
> + if (ret)
> + goto clk_disable;
> +
> + sdhci_sprd_init_config(host);
> + host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
> + sprd_host->version = ((host->version & SDHCI_VENDOR_VER_MASK) >>
> + SDHCI_VENDOR_VER_SHIFT);
> +
> + pm_runtime_get_noresume(&pdev->dev);
> + pm_runtime_set_active(&pdev->dev);
> + pm_runtime_enable(&pdev->dev);
> + pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
> + pm_runtime_use_autosuspend(&pdev->dev);
> + pm_suspend_ignore_children(&pdev->dev, 1);
> +
> + sdhci_enable_v4_mode(host);
> + ret = sdhci_add_host(host);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to add mmc host: %d\n", ret);
> + goto pm_runtime_disable;
> + }
> +
> + pm_runtime_mark_last_busy(&pdev->dev);
> + pm_runtime_put_autosuspend(&pdev->dev);
> +
> + return 0;
> +
> +pm_runtime_disable:
> + pm_runtime_disable(&pdev->dev);
> + pm_runtime_set_suspended(&pdev->dev);
> +
> + clk_disable_unprepare(sprd_host->clk_enable);
> +
> +clk_disable:
> + clk_disable_unprepare(sprd_host->clk_sdio);
> +
> +pltfm_free:
> + sdhci_pltfm_free(pdev);
> + return ret;
> +}
> +
> +static int sdhci_sprd_remove(struct platform_device *pdev)
> +{
> + struct sdhci_host *host = platform_get_drvdata(pdev);
> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
> + struct mmc_host *mmc = host->mmc;
> +
> + mmc_remove_host(mmc);
> + clk_disable_unprepare(sprd_host->clk_sdio);
> + clk_disable_unprepare(sprd_host->clk_enable);
> +
> + mmc_free_host(mmc);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id sdhci_sprd_of_match[] = {
> + { .compatible = "sprd,sdhci-r11", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, sdhci_sprd_of_match);
> +
> +#ifdef CONFIG_PM
> +static int sdhci_sprd_runtime_suspend(struct device *dev)
> +{
> + struct sdhci_host *host = dev_get_drvdata(dev);
> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
> +
> + sdhci_runtime_suspend_host(host);
> +
> + clk_disable_unprepare(sprd_host->clk_sdio);
> + clk_disable_unprepare(sprd_host->clk_enable);
> +
> + return 0;
> +}
> +
> +static int sdhci_sprd_runtime_resume(struct device *dev)
> +{
> + struct sdhci_host *host = dev_get_drvdata(dev);
> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
> +
> + clk_prepare_enable(sprd_host->clk_enable);
> + clk_prepare_enable(sprd_host->clk_sdio);

Drivers usually check the error returns of clk_prepare_enable().

> +
> + sdhci_runtime_resume_host(host);
> +
> + return 0;
> +}
> +#endif
> +
> +static const struct dev_pm_ops sdhci_sprd_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> + pm_runtime_force_resume)
> + SET_RUNTIME_PM_OPS(sdhci_sprd_runtime_suspend,
> + sdhci_sprd_runtime_resume, NULL)
> +};
> +
> +static struct platform_driver sdhci_sprd_driver = {
> + .probe = sdhci_sprd_probe,
> + .remove = sdhci_sprd_remove,
> + .driver = {
> + .name = "sdhci_sprd_r11",
> + .of_match_table = of_match_ptr(sdhci_sprd_of_match),
> + .pm = &sdhci_sprd_pm_ops,
> + },
> +};
> +module_platform_driver(sdhci_sprd_driver);
> +
> +MODULE_DESCRIPTION("Spreadtrum sdio host controller r11 driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:sdhci-sprd-r11");
>


2018-07-18 07:26:00

by Chunyan Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 6/7] mmc: sdhci-sprd: added Spreadtrum's initial host controller

On 17 July 2018 at 19:52, Adrian Hunter <[email protected]> wrote:
> On 09/07/18 06:19, Chunyan Zhang wrote:
>> From: Chunyan Zhang <[email protected]>
>>
>> This patch adds the initial support of Secure Digital Host Controller
>> Interface compliant controller found in some latest Spreadtrum chipsets.
>> This patch has been tested on the version of SPRD-R11 controller.
>>
>> R11 is a variant based on SD v4.0 specification.
>>
>> With this driver, R11 mmc can be initialized, can be mounted, read and
>> written.
>>
>> Original-by: Billows Wu <[email protected]>
>> Signed-off-by: Chunyan Zhang <[email protected]>
>> ---
>> drivers/mmc/host/Kconfig | 13 ++
>> drivers/mmc/host/Makefile | 1 +
>> drivers/mmc/host/sdhci-sprd.c | 437 ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 451 insertions(+)
>> create mode 100644 drivers/mmc/host/sdhci-sprd.c
>>
>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>> index 0581c19..c5424dc 100644
>> --- a/drivers/mmc/host/Kconfig
>> +++ b/drivers/mmc/host/Kconfig
>> @@ -581,6 +581,19 @@ config MMC_SDRICOH_CS
>> To compile this driver as a module, choose M here: the
>> module will be called sdricoh_cs.
>>
>> +config MMC_SDHCI_SPRD
>> + tristate "Spreadtrum SDIO host Controller"
>> + depends on ARCH_SPRD
>> + depends on MMC_SDHCI_PLTFM
>> + select MMC_SDHCI_IO_ACCESSORS
>> + help
>> + This selects the SDIO Host Controller in Spreadtrum
>> + SoCs, this driver supports R11(IP version: R11P0).
>> +
>> + If you have a controller with this interface, say Y or M here.
>> +
>> + If unsure, say N.
>> +
>> config MMC_TMIO_CORE
>> tristate
>>
>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>> index 85dc132..b0b6802 100644
>> --- a/drivers/mmc/host/Makefile
>> +++ b/drivers/mmc/host/Makefile
>> @@ -89,6 +89,7 @@ obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
>> obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o
>> obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
>> obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o
>> +obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
>> obj-$(CONFIG_MMC_CQHCI) += cqhci.o
>>
>> ifeq ($(CONFIG_CB710_DEBUG),y)
>> diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c
>> new file mode 100644
>> index 0000000..8d37ecb
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-sprd.c
>> @@ -0,0 +1,437 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +//
>> +// Secure Digital Host Controller
>> +//
>> +// Copyright (C) 2018 Spreadtrum, Inc.
>> +// Author: Chunyan Zhang <[email protected]>
>> +
>> +#include <linux/delay.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/highmem.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/slab.h>
>> +
>> +#include "sdhci-pltfm.h"
>> +
>> +#define SDHCI_SPRD_REG_32_DLL_DLY_OFFSET 0x208
>> +#define SDHCIBSPRD_IT_WR_DLY_INV BIT(5)
>> +#define SDHCI_SPRD_BIT_CMD_DLY_INV BIT(13)
>> +#define SDHCI_SPRD_BIT_POSRD_DLY_INV BIT(21)
>> +#define SDHCI_SPRD_BIT_NEGRD_DLY_INV BIT(29)
>> +
>> +#define SDHCI_SPRD_REG_32_BUSY_POSI 0x250
>> +#define SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN BIT(25)
>> +#define SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN BIT(24)
>> +
>> +#define SDHCI_SPRD_REG_DEBOUNCE 0x28C
>> +#define SDHCI_SPRD_BIT_DLL_BAK BIT(0)
>> +#define SDHCI_SPRD_BIT_DLL_VAL BIT(1)
>> +
>> +#define SDHCI_SPRD_INT_SIGNAL_MASK 0x1B7F410B
>> +
>> +/* SDHCI_HOST_CONTROL2 */
>> +#define SDHCI_SPRD_CTRL_HS200 0x0005
>> +#define SDHCI_SPRD_CTRL_HS400 0x0006
>> +
>> +/* SDHCI_SOFTWARE_RESET */
>> +#define SDHCI_HW_RESET_CARD 0x8 /* For Spreadtrum's design */
>> +
>> +#define SDHCI_SPRD_MAX_CUR 0xFFFFFF
>> +#define SDHCI_SPRD_CLK_MAX_DIV 1023
>> +
>> +#define SDHCI_SPRD_CLK_DEF_RATE 26000000
>> +
>> +struct sdhci_sprd_host {
>> + u32 version;
>> + struct clk *clk_sdio;
>> + struct clk *clk_enable;
>> + u32 base_rate;
>> +};
>> +
>> +#define TO_SPRD_HOST(host) sdhci_pltfm_priv(sdhci_priv(host))
>> +
>> +static void sdhci_sprd_init_config(struct sdhci_host *host)
>> +{
>> + u16 val;
>> +
>> + /* set 64-bit addressing modes */
>> + val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
>> + val |= SDHCI_CTRL_64BIT_ADDR;
>> + sdhci_writew(host, val, SDHCI_HOST_CONTROL2);
>
> SDHCI should do that when enabling 64-bit DMA in V4 mode. Needs to be done
> either in sdhci_prepare_data() or after every reset or resume (e.g. when
> ->enable_dma() is called).

Ok, then how about doing in sdhci_do_reset() once reset-for-all? That
also can avoid to set this register each time when sending cmd if
doing in sdhci_prepare_data().

I also prefer to move writing of SDHCI_HOST_CONTROL from
sdhci_prepare_data() to sdhci_do_reset(), can that work properly?
Since I noticed that flags of DMA (ADMA/SDMA) were set during
initialization (i.e. probe).

What's your opinion?

>
>> +
>> + /* set dll backup mode */
>> + val = sdhci_readl(host, SDHCI_SPRD_REG_DEBOUNCE);
>> + val |= SDHCI_SPRD_BIT_DLL_BAK | SDHCI_SPRD_BIT_DLL_VAL;
>> + sdhci_writel(host, val, SDHCI_SPRD_REG_DEBOUNCE);
>> +
>> + /* set CMD23 enabled */
>> + val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
>> + val |= SDHCI_CMD23_ENABLE;
>> + sdhci_writew(host, val, SDHCI_HOST_CONTROL2);
>
> SDHCI should do that. Needs to be done either when enabling auto-CMD23 or
> after every reset or resume.

Ok, will do in sdhci_do_reset() as well.

>
>> +}
>> +
>> +static inline u32 sdhci_sprd_readl(struct sdhci_host *host, int reg)
>> +{
>> + if (unlikely(reg == SDHCI_MAX_CURRENT))
>> + return SDHCI_SPRD_MAX_CUR;
>> +
>> + return readl_relaxed(host->ioaddr + reg);
>> +}
>> +
>> +static inline void sdhci_sprd_writel(struct sdhci_host *host, u32 val, int reg)
>> +{
>> + /* SDHCI_MAX_CURRENT is reserved on Spreadtrum's platform */
>> + if (unlikely(reg == SDHCI_MAX_CURRENT))
>> + return;
>> +
>> + if (unlikely(reg == SDHCI_SIGNAL_ENABLE || reg == SDHCI_INT_ENABLE))
>> + val = val & SDHCI_SPRD_INT_SIGNAL_MASK;
>> +
>> + writel_relaxed(val, host->ioaddr + reg);
>> +}
>> +
>> +static inline void sdhci_sprd_writeb(struct sdhci_host *host, u8 val, int reg)
>> +{
>> + if (unlikely(reg == SDHCI_SOFTWARE_RESET)) {
>> + if (readb_relaxed(host->ioaddr + reg) & SDHCI_HW_RESET_CARD)
>> + val |= SDHCI_HW_RESET_CARD;
>> + }
>> +
>> + writeb_relaxed(val, host->ioaddr + reg);
>> +}
>> +
>> +static inline void sdhci_sprd_sd_clk_off(struct sdhci_host *host)
>> +{
>> + u16 ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
>> +
>> + ctrl &= ~SDHCI_CLOCK_CARD_EN;
>> + sdhci_writew(host, ctrl, SDHCI_CLOCK_CONTROL);
>> +}
>> +
>> +static inline void
>> +sdhci_sprd_set_dll_invert(struct sdhci_host *host, u32 mask, bool en)
>> +{
>> + u32 dll_dly_offset;
>> +
>> + dll_dly_offset = sdhci_readl(host, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
>> + if (en)
>> + dll_dly_offset |= mask;
>> + else
>> + dll_dly_offset &= ~mask;
>> + sdhci_writel(host, dll_dly_offset, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
>> +}
>> +
>> +static inline u32 sdhci_sprd_calc_div(u32 base_clk, u32 clk)
>> +{
>> + u32 div;
>> +
>> + /* select 2x clock source */
>> + if (base_clk <= clk * 2)
>> + return 0;
>> +
>> + div = (u32) (base_clk / (clk * 2));
>> +
>> + if ((base_clk / div) > (clk * 2))
>> + div++;
>> +
>> + if (div > SDHCI_SPRD_CLK_MAX_DIV)
>> + div = SDHCI_SPRD_CLK_MAX_DIV;
>> +
>> + if (div % 2)
>> + div = (div + 1) / 2;
>> + else
>> + div = div / 2;
>> +
>> + return div;
>> +}
>> +
>> +static inline void _sdhci_sprd_set_clock(struct sdhci_host *host,
>> + unsigned int clk)
>> +{
>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>> + u32 div, val, mask;
>> +
>> + div = sdhci_sprd_calc_div(sprd_host->base_rate, clk);
>> +
>> + clk |= ((div & 0x300) >> 2) | ((div & 0xFF) << 8);
>> + sdhci_enable_clk(host, clk);
>> +
>> + /* enable auto gate sdhc_enable_auto_gate */
>> + val = sdhci_readl(host, SDHCI_SPRD_REG_32_BUSY_POSI);
>> + mask = SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN |
>> + SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN;
>> + if (mask != (val & mask)) {
>> + val |= mask;
>> + sdhci_writel(host, val, SDHCI_SPRD_REG_32_BUSY_POSI);
>> + }
>> +}
>> +
>> +static void sdhci_sprd_set_clock(struct sdhci_host *host, unsigned int clock)
>> +{
>> + bool en = false;
>> +
>> + if (clock == 0) {
>> + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
>> + } else if (clock != host->clock) {
>> + sdhci_sprd_sd_clk_off(host);
>> + _sdhci_sprd_set_clock(host, clock);
>> +
>> + if (clock <= 400000)
>> + en = true;
>> + sdhci_sprd_set_dll_invert(host, SDHCI_SPRD_BIT_CMD_DLY_INV |
>> + SDHCI_SPRD_BIT_POSRD_DLY_INV, en);
>> + } else {
>> + _sdhci_sprd_set_clock(host, clock);
>> + }
>> +}
>> +
>> +static unsigned int sdhci_sprd_get_max_clock(struct sdhci_host *host)
>> +{
>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>> +
>> + return clk_round_rate(sprd_host->clk_sdio, ULONG_MAX);
>> +}
>> +
>> +static unsigned int sdhci_sprd_get_min_clock(struct sdhci_host *host)
>> +{
>> + return 400000;
>> +}
>> +
>> +static void sdhci_sprd_set_uhs_signaling(struct sdhci_host *host,
>> + unsigned int timing)
>> +{
>> + u16 ctrl_2;
>> +
>> + if (timing == host->timing)
>> + return;
>> +
>> + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
>> + /* Select Bus Speed Mode for host */
>> + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
>> + switch (timing) {
>> + case MMC_TIMING_UHS_SDR12:
>> + ctrl_2 = SDHCI_CTRL_UHS_SDR12;
>
> Shouldn't this and the following ones be '|=' not '='
>
>> + break;
>> + case MMC_TIMING_MMC_HS:
>> + case MMC_TIMING_SD_HS:
>> + case MMC_TIMING_UHS_SDR25:
>> + ctrl_2 = SDHCI_CTRL_UHS_SDR25;
>> + break;
>> + case MMC_TIMING_UHS_SDR50:
>> + ctrl_2 = SDHCI_CTRL_UHS_SDR50;
>> + break;
>> + case MMC_TIMING_UHS_SDR104:
>> + ctrl_2 = SDHCI_CTRL_UHS_SDR104;
>> + break;
>> + case MMC_TIMING_UHS_DDR50:
>> + case MMC_TIMING_MMC_DDR52:
>> + ctrl_2 = SDHCI_CTRL_UHS_DDR50;
>> + break;
>> + case MMC_TIMING_MMC_HS200:
>> + ctrl_2 = SDHCI_SPRD_CTRL_HS200;
>> + break;
>> + case MMC_TIMING_MMC_HS400:
>> + ctrl_2 = SDHCI_SPRD_CTRL_HS400;
>> + break;
>> + default:
>> + break;
>> + }
>> +
>> + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
>> +}
>> +
>> +static void sdhci_sprd_hw_reset(struct sdhci_host *host)
>> +{
>> + int val;
>> +
>> + /* Note: don't use sdhci_readb/writeb() API here */
>> + val = readb_relaxed(host->ioaddr + SDHCI_SOFTWARE_RESET);
>> + val &= ~SDHCI_HW_RESET_CARD;
>> + writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
>> + udelay(10);
>> +
>> + val |= SDHCI_HW_RESET_CARD;
>> + writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
>> + udelay(300);
>
> usleep_range() maybe

Ok, I will have another look at it.

Thanks for the comments,
Chunyan

>
>> +}
>> +
>> +static struct sdhci_ops sdhci_sprd_ops = {
>> + .read_l = sdhci_sprd_readl,
>> + .write_l = sdhci_sprd_writel,
>> + .write_b = sdhci_sprd_writeb,
>> + .set_clock = sdhci_sprd_set_clock,
>> + .get_max_clock = sdhci_sprd_get_max_clock,
>> + .get_min_clock = sdhci_sprd_get_min_clock,
>> + .set_bus_width = sdhci_set_bus_width,
>> + .reset = sdhci_reset,
>> + .set_uhs_signaling = sdhci_sprd_set_uhs_signaling,
>> + .hw_reset = sdhci_sprd_hw_reset,
>> +};
>> +
>> +static const struct sdhci_pltfm_data sdhci_sprd_pdata = {
>> + .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK,
>> + .quirks2 = SDHCI_QUIRK2_BROKEN_HS200,
>> + .ops = &sdhci_sprd_ops,
>> +};
>> +
>> +static int sdhci_sprd_probe(struct platform_device *pdev)
>> +{
>> + struct sdhci_host *host;
>> + struct sdhci_sprd_host *sprd_host;
>> + struct clk *clk;
>> + int ret = 0;
>> +
>> + host = sdhci_pltfm_init(pdev, &sdhci_sprd_pdata, sizeof(*sprd_host));
>> + if (IS_ERR(host))
>> + return PTR_ERR(host);
>> +
>> + host->dma_mask = DMA_BIT_MASK(64);
>> + pdev->dev.dma_mask = &host->dma_mask;
>> +
>> + host->mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
>> + MMC_CAP_ERASE | MMC_CAP_CMD23;
>> + ret = mmc_of_parse(host->mmc);
>> + if (ret)
>> + goto pltfm_free;
>> +
>> + sprd_host = TO_SPRD_HOST(host);
>> +
>> + clk = devm_clk_get(&pdev->dev, "sdio");
>> + if (IS_ERR(clk)) {
>> + ret = PTR_ERR(clk);
>> + goto pltfm_free;
>> + }
>> + sprd_host->clk_sdio = clk;
>> + sprd_host->base_rate = clk_get_rate(sprd_host->clk_sdio);
>> + if (!sprd_host->base_rate)
>> + sprd_host->base_rate = SDHCI_SPRD_CLK_DEF_RATE;
>> +
>> + clk = devm_clk_get(&pdev->dev, "enable");
>> + if (IS_ERR(clk)) {
>> + ret = PTR_ERR(clk);
>> + goto pltfm_free;
>> + }
>> + sprd_host->clk_enable = clk;
>> +
>> + ret = clk_prepare_enable(sprd_host->clk_sdio);
>> + if (ret)
>> + goto pltfm_free;
>> +
>> + clk_prepare_enable(sprd_host->clk_enable);
>> + if (ret)
>> + goto clk_disable;
>> +
>> + sdhci_sprd_init_config(host);
>> + host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
>> + sprd_host->version = ((host->version & SDHCI_VENDOR_VER_MASK) >>
>> + SDHCI_VENDOR_VER_SHIFT);
>> +
>> + pm_runtime_get_noresume(&pdev->dev);
>> + pm_runtime_set_active(&pdev->dev);
>> + pm_runtime_enable(&pdev->dev);
>> + pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
>> + pm_runtime_use_autosuspend(&pdev->dev);
>> + pm_suspend_ignore_children(&pdev->dev, 1);
>> +
>> + sdhci_enable_v4_mode(host);
>> + ret = sdhci_add_host(host);
>> + if (ret) {
>> + dev_err(&pdev->dev, "failed to add mmc host: %d\n", ret);
>> + goto pm_runtime_disable;
>> + }
>> +
>> + pm_runtime_mark_last_busy(&pdev->dev);
>> + pm_runtime_put_autosuspend(&pdev->dev);
>> +
>> + return 0;
>> +
>> +pm_runtime_disable:
>> + pm_runtime_disable(&pdev->dev);
>> + pm_runtime_set_suspended(&pdev->dev);
>> +
>> + clk_disable_unprepare(sprd_host->clk_enable);
>> +
>> +clk_disable:
>> + clk_disable_unprepare(sprd_host->clk_sdio);
>> +
>> +pltfm_free:
>> + sdhci_pltfm_free(pdev);
>> + return ret;
>> +}
>> +
>> +static int sdhci_sprd_remove(struct platform_device *pdev)
>> +{
>> + struct sdhci_host *host = platform_get_drvdata(pdev);
>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>> + struct mmc_host *mmc = host->mmc;
>> +
>> + mmc_remove_host(mmc);
>> + clk_disable_unprepare(sprd_host->clk_sdio);
>> + clk_disable_unprepare(sprd_host->clk_enable);
>> +
>> + mmc_free_host(mmc);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id sdhci_sprd_of_match[] = {
>> + { .compatible = "sprd,sdhci-r11", },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(of, sdhci_sprd_of_match);
>> +
>> +#ifdef CONFIG_PM
>> +static int sdhci_sprd_runtime_suspend(struct device *dev)
>> +{
>> + struct sdhci_host *host = dev_get_drvdata(dev);
>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>> +
>> + sdhci_runtime_suspend_host(host);
>> +
>> + clk_disable_unprepare(sprd_host->clk_sdio);
>> + clk_disable_unprepare(sprd_host->clk_enable);
>> +
>> + return 0;
>> +}
>> +
>> +static int sdhci_sprd_runtime_resume(struct device *dev)
>> +{
>> + struct sdhci_host *host = dev_get_drvdata(dev);
>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>> +
>> + clk_prepare_enable(sprd_host->clk_enable);
>> + clk_prepare_enable(sprd_host->clk_sdio);
>
> Drivers usually check the error returns of clk_prepare_enable().
>
>> +
>> + sdhci_runtime_resume_host(host);
>> +
>> + return 0;
>> +}
>> +#endif
>> +
>> +static const struct dev_pm_ops sdhci_sprd_pm_ops = {
>> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> + pm_runtime_force_resume)
>> + SET_RUNTIME_PM_OPS(sdhci_sprd_runtime_suspend,
>> + sdhci_sprd_runtime_resume, NULL)
>> +};
>> +
>> +static struct platform_driver sdhci_sprd_driver = {
>> + .probe = sdhci_sprd_probe,
>> + .remove = sdhci_sprd_remove,
>> + .driver = {
>> + .name = "sdhci_sprd_r11",
>> + .of_match_table = of_match_ptr(sdhci_sprd_of_match),
>> + .pm = &sdhci_sprd_pm_ops,
>> + },
>> +};
>> +module_platform_driver(sdhci_sprd_driver);
>> +
>> +MODULE_DESCRIPTION("Spreadtrum sdio host controller r11 driver");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_ALIAS("platform:sdhci-sprd-r11");
>>
>

2018-07-18 07:51:26

by Adrian Hunter

[permalink] [raw]
Subject: Re: [PATCH V3 6/7] mmc: sdhci-sprd: added Spreadtrum's initial host controller

On 18/07/18 10:24, Chunyan Zhang wrote:
> On 17 July 2018 at 19:52, Adrian Hunter <[email protected]> wrote:
>> On 09/07/18 06:19, Chunyan Zhang wrote:
>>> From: Chunyan Zhang <[email protected]>
>>>
>>> This patch adds the initial support of Secure Digital Host Controller
>>> Interface compliant controller found in some latest Spreadtrum chipsets.
>>> This patch has been tested on the version of SPRD-R11 controller.
>>>
>>> R11 is a variant based on SD v4.0 specification.
>>>
>>> With this driver, R11 mmc can be initialized, can be mounted, read and
>>> written.
>>>
>>> Original-by: Billows Wu <[email protected]>
>>> Signed-off-by: Chunyan Zhang <[email protected]>
>>> ---
>>> drivers/mmc/host/Kconfig | 13 ++
>>> drivers/mmc/host/Makefile | 1 +
>>> drivers/mmc/host/sdhci-sprd.c | 437 ++++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 451 insertions(+)
>>> create mode 100644 drivers/mmc/host/sdhci-sprd.c
>>>
>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>>> index 0581c19..c5424dc 100644
>>> --- a/drivers/mmc/host/Kconfig
>>> +++ b/drivers/mmc/host/Kconfig
>>> @@ -581,6 +581,19 @@ config MMC_SDRICOH_CS
>>> To compile this driver as a module, choose M here: the
>>> module will be called sdricoh_cs.
>>>
>>> +config MMC_SDHCI_SPRD
>>> + tristate "Spreadtrum SDIO host Controller"
>>> + depends on ARCH_SPRD
>>> + depends on MMC_SDHCI_PLTFM
>>> + select MMC_SDHCI_IO_ACCESSORS
>>> + help
>>> + This selects the SDIO Host Controller in Spreadtrum
>>> + SoCs, this driver supports R11(IP version: R11P0).
>>> +
>>> + If you have a controller with this interface, say Y or M here.
>>> +
>>> + If unsure, say N.
>>> +
>>> config MMC_TMIO_CORE
>>> tristate
>>>
>>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>>> index 85dc132..b0b6802 100644
>>> --- a/drivers/mmc/host/Makefile
>>> +++ b/drivers/mmc/host/Makefile
>>> @@ -89,6 +89,7 @@ obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
>>> obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o
>>> obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
>>> obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o
>>> +obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
>>> obj-$(CONFIG_MMC_CQHCI) += cqhci.o
>>>
>>> ifeq ($(CONFIG_CB710_DEBUG),y)
>>> diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c
>>> new file mode 100644
>>> index 0000000..8d37ecb
>>> --- /dev/null
>>> +++ b/drivers/mmc/host/sdhci-sprd.c
>>> @@ -0,0 +1,437 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +//
>>> +// Secure Digital Host Controller
>>> +//
>>> +// Copyright (C) 2018 Spreadtrum, Inc.
>>> +// Author: Chunyan Zhang <[email protected]>
>>> +
>>> +#include <linux/delay.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/highmem.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/of_gpio.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/slab.h>
>>> +
>>> +#include "sdhci-pltfm.h"
>>> +
>>> +#define SDHCI_SPRD_REG_32_DLL_DLY_OFFSET 0x208
>>> +#define SDHCIBSPRD_IT_WR_DLY_INV BIT(5)
>>> +#define SDHCI_SPRD_BIT_CMD_DLY_INV BIT(13)
>>> +#define SDHCI_SPRD_BIT_POSRD_DLY_INV BIT(21)
>>> +#define SDHCI_SPRD_BIT_NEGRD_DLY_INV BIT(29)
>>> +
>>> +#define SDHCI_SPRD_REG_32_BUSY_POSI 0x250
>>> +#define SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN BIT(25)
>>> +#define SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN BIT(24)
>>> +
>>> +#define SDHCI_SPRD_REG_DEBOUNCE 0x28C
>>> +#define SDHCI_SPRD_BIT_DLL_BAK BIT(0)
>>> +#define SDHCI_SPRD_BIT_DLL_VAL BIT(1)
>>> +
>>> +#define SDHCI_SPRD_INT_SIGNAL_MASK 0x1B7F410B
>>> +
>>> +/* SDHCI_HOST_CONTROL2 */
>>> +#define SDHCI_SPRD_CTRL_HS200 0x0005
>>> +#define SDHCI_SPRD_CTRL_HS400 0x0006
>>> +
>>> +/* SDHCI_SOFTWARE_RESET */
>>> +#define SDHCI_HW_RESET_CARD 0x8 /* For Spreadtrum's design */
>>> +
>>> +#define SDHCI_SPRD_MAX_CUR 0xFFFFFF
>>> +#define SDHCI_SPRD_CLK_MAX_DIV 1023
>>> +
>>> +#define SDHCI_SPRD_CLK_DEF_RATE 26000000
>>> +
>>> +struct sdhci_sprd_host {
>>> + u32 version;
>>> + struct clk *clk_sdio;
>>> + struct clk *clk_enable;
>>> + u32 base_rate;
>>> +};
>>> +
>>> +#define TO_SPRD_HOST(host) sdhci_pltfm_priv(sdhci_priv(host))
>>> +
>>> +static void sdhci_sprd_init_config(struct sdhci_host *host)
>>> +{
>>> + u16 val;
>>> +
>>> + /* set 64-bit addressing modes */
>>> + val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
>>> + val |= SDHCI_CTRL_64BIT_ADDR;
>>> + sdhci_writew(host, val, SDHCI_HOST_CONTROL2);
>>
>> SDHCI should do that when enabling 64-bit DMA in V4 mode. Needs to be done
>> either in sdhci_prepare_data() or after every reset or resume (e.g. when
>> ->enable_dma() is called).
>
> Ok, then how about doing in sdhci_do_reset() once reset-for-all? That
> also can avoid to set this register each time when sending cmd if
> doing in sdhci_prepare_data().
>
> I also prefer to move writing of SDHCI_HOST_CONTROL from
> sdhci_prepare_data() to sdhci_do_reset(), can that work properly?
> Since I noticed that flags of DMA (ADMA/SDMA) were set during
> initialization (i.e. probe).
>
> What's your opinion?

Actually, looking at the code now, it looks like sdhci_init() is a better
place than sdhci_do_reset().

>
>>
>>> +
>>> + /* set dll backup mode */
>>> + val = sdhci_readl(host, SDHCI_SPRD_REG_DEBOUNCE);
>>> + val |= SDHCI_SPRD_BIT_DLL_BAK | SDHCI_SPRD_BIT_DLL_VAL;
>>> + sdhci_writel(host, val, SDHCI_SPRD_REG_DEBOUNCE);
>>> +
>>> + /* set CMD23 enabled */
>>> + val = sdhci_readw(host, SDHCI_HOST_CONTROL2);
>>> + val |= SDHCI_CMD23_ENABLE;
>>> + sdhci_writew(host, val, SDHCI_HOST_CONTROL2);
>>
>> SDHCI should do that. Needs to be done either when enabling auto-CMD23 or
>> after every reset or resume.
>
> Ok, will do in sdhci_do_reset() as well.

That one is tricky since it needs to be based on the capability of the card.
But again the setting can be done in sdhci_init().

>
>>
>>> +}
>>> +
>>> +static inline u32 sdhci_sprd_readl(struct sdhci_host *host, int reg)
>>> +{
>>> + if (unlikely(reg == SDHCI_MAX_CURRENT))
>>> + return SDHCI_SPRD_MAX_CUR;
>>> +
>>> + return readl_relaxed(host->ioaddr + reg);
>>> +}
>>> +
>>> +static inline void sdhci_sprd_writel(struct sdhci_host *host, u32 val, int reg)
>>> +{
>>> + /* SDHCI_MAX_CURRENT is reserved on Spreadtrum's platform */
>>> + if (unlikely(reg == SDHCI_MAX_CURRENT))
>>> + return;
>>> +
>>> + if (unlikely(reg == SDHCI_SIGNAL_ENABLE || reg == SDHCI_INT_ENABLE))
>>> + val = val & SDHCI_SPRD_INT_SIGNAL_MASK;
>>> +
>>> + writel_relaxed(val, host->ioaddr + reg);
>>> +}
>>> +
>>> +static inline void sdhci_sprd_writeb(struct sdhci_host *host, u8 val, int reg)
>>> +{
>>> + if (unlikely(reg == SDHCI_SOFTWARE_RESET)) {
>>> + if (readb_relaxed(host->ioaddr + reg) & SDHCI_HW_RESET_CARD)
>>> + val |= SDHCI_HW_RESET_CARD;
>>> + }
>>> +
>>> + writeb_relaxed(val, host->ioaddr + reg);
>>> +}
>>> +
>>> +static inline void sdhci_sprd_sd_clk_off(struct sdhci_host *host)
>>> +{
>>> + u16 ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
>>> +
>>> + ctrl &= ~SDHCI_CLOCK_CARD_EN;
>>> + sdhci_writew(host, ctrl, SDHCI_CLOCK_CONTROL);
>>> +}
>>> +
>>> +static inline void
>>> +sdhci_sprd_set_dll_invert(struct sdhci_host *host, u32 mask, bool en)
>>> +{
>>> + u32 dll_dly_offset;
>>> +
>>> + dll_dly_offset = sdhci_readl(host, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
>>> + if (en)
>>> + dll_dly_offset |= mask;
>>> + else
>>> + dll_dly_offset &= ~mask;
>>> + sdhci_writel(host, dll_dly_offset, SDHCI_SPRD_REG_32_DLL_DLY_OFFSET);
>>> +}
>>> +
>>> +static inline u32 sdhci_sprd_calc_div(u32 base_clk, u32 clk)
>>> +{
>>> + u32 div;
>>> +
>>> + /* select 2x clock source */
>>> + if (base_clk <= clk * 2)
>>> + return 0;
>>> +
>>> + div = (u32) (base_clk / (clk * 2));
>>> +
>>> + if ((base_clk / div) > (clk * 2))
>>> + div++;
>>> +
>>> + if (div > SDHCI_SPRD_CLK_MAX_DIV)
>>> + div = SDHCI_SPRD_CLK_MAX_DIV;
>>> +
>>> + if (div % 2)
>>> + div = (div + 1) / 2;
>>> + else
>>> + div = div / 2;
>>> +
>>> + return div;
>>> +}
>>> +
>>> +static inline void _sdhci_sprd_set_clock(struct sdhci_host *host,
>>> + unsigned int clk)
>>> +{
>>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>>> + u32 div, val, mask;
>>> +
>>> + div = sdhci_sprd_calc_div(sprd_host->base_rate, clk);
>>> +
>>> + clk |= ((div & 0x300) >> 2) | ((div & 0xFF) << 8);
>>> + sdhci_enable_clk(host, clk);
>>> +
>>> + /* enable auto gate sdhc_enable_auto_gate */
>>> + val = sdhci_readl(host, SDHCI_SPRD_REG_32_BUSY_POSI);
>>> + mask = SDHCI_SPRD_BIT_OUTR_CLK_AUTO_EN |
>>> + SDHCI_SPRD_BIT_INNR_CLK_AUTO_EN;
>>> + if (mask != (val & mask)) {
>>> + val |= mask;
>>> + sdhci_writel(host, val, SDHCI_SPRD_REG_32_BUSY_POSI);
>>> + }
>>> +}
>>> +
>>> +static void sdhci_sprd_set_clock(struct sdhci_host *host, unsigned int clock)
>>> +{
>>> + bool en = false;
>>> +
>>> + if (clock == 0) {
>>> + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
>>> + } else if (clock != host->clock) {
>>> + sdhci_sprd_sd_clk_off(host);
>>> + _sdhci_sprd_set_clock(host, clock);
>>> +
>>> + if (clock <= 400000)
>>> + en = true;
>>> + sdhci_sprd_set_dll_invert(host, SDHCI_SPRD_BIT_CMD_DLY_INV |
>>> + SDHCI_SPRD_BIT_POSRD_DLY_INV, en);
>>> + } else {
>>> + _sdhci_sprd_set_clock(host, clock);
>>> + }
>>> +}
>>> +
>>> +static unsigned int sdhci_sprd_get_max_clock(struct sdhci_host *host)
>>> +{
>>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>>> +
>>> + return clk_round_rate(sprd_host->clk_sdio, ULONG_MAX);
>>> +}
>>> +
>>> +static unsigned int sdhci_sprd_get_min_clock(struct sdhci_host *host)
>>> +{
>>> + return 400000;
>>> +}
>>> +
>>> +static void sdhci_sprd_set_uhs_signaling(struct sdhci_host *host,
>>> + unsigned int timing)
>>> +{
>>> + u16 ctrl_2;
>>> +
>>> + if (timing == host->timing)
>>> + return;
>>> +
>>> + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
>>> + /* Select Bus Speed Mode for host */
>>> + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
>>> + switch (timing) {
>>> + case MMC_TIMING_UHS_SDR12:
>>> + ctrl_2 = SDHCI_CTRL_UHS_SDR12;
>>
>> Shouldn't this and the following ones be '|=' not '='
>>
>>> + break;
>>> + case MMC_TIMING_MMC_HS:
>>> + case MMC_TIMING_SD_HS:
>>> + case MMC_TIMING_UHS_SDR25:
>>> + ctrl_2 = SDHCI_CTRL_UHS_SDR25;
>>> + break;
>>> + case MMC_TIMING_UHS_SDR50:
>>> + ctrl_2 = SDHCI_CTRL_UHS_SDR50;
>>> + break;
>>> + case MMC_TIMING_UHS_SDR104:
>>> + ctrl_2 = SDHCI_CTRL_UHS_SDR104;
>>> + break;
>>> + case MMC_TIMING_UHS_DDR50:
>>> + case MMC_TIMING_MMC_DDR52:
>>> + ctrl_2 = SDHCI_CTRL_UHS_DDR50;
>>> + break;
>>> + case MMC_TIMING_MMC_HS200:
>>> + ctrl_2 = SDHCI_SPRD_CTRL_HS200;
>>> + break;
>>> + case MMC_TIMING_MMC_HS400:
>>> + ctrl_2 = SDHCI_SPRD_CTRL_HS400;
>>> + break;
>>> + default:
>>> + break;
>>> + }
>>> +
>>> + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
>>> +}
>>> +
>>> +static void sdhci_sprd_hw_reset(struct sdhci_host *host)
>>> +{
>>> + int val;
>>> +
>>> + /* Note: don't use sdhci_readb/writeb() API here */
>>> + val = readb_relaxed(host->ioaddr + SDHCI_SOFTWARE_RESET);
>>> + val &= ~SDHCI_HW_RESET_CARD;
>>> + writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
>>> + udelay(10);
>>> +
>>> + val |= SDHCI_HW_RESET_CARD;
>>> + writeb_relaxed(val, host->ioaddr + SDHCI_SOFTWARE_RESET);
>>> + udelay(300);
>>
>> usleep_range() maybe
>
> Ok, I will have another look at it.
>
> Thanks for the comments,
> Chunyan
>
>>
>>> +}
>>> +
>>> +static struct sdhci_ops sdhci_sprd_ops = {
>>> + .read_l = sdhci_sprd_readl,
>>> + .write_l = sdhci_sprd_writel,
>>> + .write_b = sdhci_sprd_writeb,
>>> + .set_clock = sdhci_sprd_set_clock,
>>> + .get_max_clock = sdhci_sprd_get_max_clock,
>>> + .get_min_clock = sdhci_sprd_get_min_clock,
>>> + .set_bus_width = sdhci_set_bus_width,
>>> + .reset = sdhci_reset,
>>> + .set_uhs_signaling = sdhci_sprd_set_uhs_signaling,
>>> + .hw_reset = sdhci_sprd_hw_reset,
>>> +};
>>> +
>>> +static const struct sdhci_pltfm_data sdhci_sprd_pdata = {
>>> + .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK,
>>> + .quirks2 = SDHCI_QUIRK2_BROKEN_HS200,
>>> + .ops = &sdhci_sprd_ops,
>>> +};
>>> +
>>> +static int sdhci_sprd_probe(struct platform_device *pdev)
>>> +{
>>> + struct sdhci_host *host;
>>> + struct sdhci_sprd_host *sprd_host;
>>> + struct clk *clk;
>>> + int ret = 0;
>>> +
>>> + host = sdhci_pltfm_init(pdev, &sdhci_sprd_pdata, sizeof(*sprd_host));
>>> + if (IS_ERR(host))
>>> + return PTR_ERR(host);
>>> +
>>> + host->dma_mask = DMA_BIT_MASK(64);
>>> + pdev->dev.dma_mask = &host->dma_mask;
>>> +
>>> + host->mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
>>> + MMC_CAP_ERASE | MMC_CAP_CMD23;
>>> + ret = mmc_of_parse(host->mmc);
>>> + if (ret)
>>> + goto pltfm_free;
>>> +
>>> + sprd_host = TO_SPRD_HOST(host);
>>> +
>>> + clk = devm_clk_get(&pdev->dev, "sdio");
>>> + if (IS_ERR(clk)) {
>>> + ret = PTR_ERR(clk);
>>> + goto pltfm_free;
>>> + }
>>> + sprd_host->clk_sdio = clk;
>>> + sprd_host->base_rate = clk_get_rate(sprd_host->clk_sdio);
>>> + if (!sprd_host->base_rate)
>>> + sprd_host->base_rate = SDHCI_SPRD_CLK_DEF_RATE;
>>> +
>>> + clk = devm_clk_get(&pdev->dev, "enable");
>>> + if (IS_ERR(clk)) {
>>> + ret = PTR_ERR(clk);
>>> + goto pltfm_free;
>>> + }
>>> + sprd_host->clk_enable = clk;
>>> +
>>> + ret = clk_prepare_enable(sprd_host->clk_sdio);
>>> + if (ret)
>>> + goto pltfm_free;
>>> +
>>> + clk_prepare_enable(sprd_host->clk_enable);
>>> + if (ret)
>>> + goto clk_disable;
>>> +
>>> + sdhci_sprd_init_config(host);
>>> + host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
>>> + sprd_host->version = ((host->version & SDHCI_VENDOR_VER_MASK) >>
>>> + SDHCI_VENDOR_VER_SHIFT);
>>> +
>>> + pm_runtime_get_noresume(&pdev->dev);
>>> + pm_runtime_set_active(&pdev->dev);
>>> + pm_runtime_enable(&pdev->dev);
>>> + pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
>>> + pm_runtime_use_autosuspend(&pdev->dev);
>>> + pm_suspend_ignore_children(&pdev->dev, 1);
>>> +
>>> + sdhci_enable_v4_mode(host);
>>> + ret = sdhci_add_host(host);
>>> + if (ret) {
>>> + dev_err(&pdev->dev, "failed to add mmc host: %d\n", ret);
>>> + goto pm_runtime_disable;
>>> + }
>>> +
>>> + pm_runtime_mark_last_busy(&pdev->dev);
>>> + pm_runtime_put_autosuspend(&pdev->dev);
>>> +
>>> + return 0;
>>> +
>>> +pm_runtime_disable:
>>> + pm_runtime_disable(&pdev->dev);
>>> + pm_runtime_set_suspended(&pdev->dev);
>>> +
>>> + clk_disable_unprepare(sprd_host->clk_enable);
>>> +
>>> +clk_disable:
>>> + clk_disable_unprepare(sprd_host->clk_sdio);
>>> +
>>> +pltfm_free:
>>> + sdhci_pltfm_free(pdev);
>>> + return ret;
>>> +}
>>> +
>>> +static int sdhci_sprd_remove(struct platform_device *pdev)
>>> +{
>>> + struct sdhci_host *host = platform_get_drvdata(pdev);
>>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>>> + struct mmc_host *mmc = host->mmc;
>>> +
>>> + mmc_remove_host(mmc);
>>> + clk_disable_unprepare(sprd_host->clk_sdio);
>>> + clk_disable_unprepare(sprd_host->clk_enable);
>>> +
>>> + mmc_free_host(mmc);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct of_device_id sdhci_sprd_of_match[] = {
>>> + { .compatible = "sprd,sdhci-r11", },
>>> + { }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, sdhci_sprd_of_match);
>>> +
>>> +#ifdef CONFIG_PM
>>> +static int sdhci_sprd_runtime_suspend(struct device *dev)
>>> +{
>>> + struct sdhci_host *host = dev_get_drvdata(dev);
>>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>>> +
>>> + sdhci_runtime_suspend_host(host);
>>> +
>>> + clk_disable_unprepare(sprd_host->clk_sdio);
>>> + clk_disable_unprepare(sprd_host->clk_enable);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sdhci_sprd_runtime_resume(struct device *dev)
>>> +{
>>> + struct sdhci_host *host = dev_get_drvdata(dev);
>>> + struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
>>> +
>>> + clk_prepare_enable(sprd_host->clk_enable);
>>> + clk_prepare_enable(sprd_host->clk_sdio);
>>
>> Drivers usually check the error returns of clk_prepare_enable().
>>
>>> +
>>> + sdhci_runtime_resume_host(host);
>>> +
>>> + return 0;
>>> +}
>>> +#endif
>>> +
>>> +static const struct dev_pm_ops sdhci_sprd_pm_ops = {
>>> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>>> + pm_runtime_force_resume)
>>> + SET_RUNTIME_PM_OPS(sdhci_sprd_runtime_suspend,
>>> + sdhci_sprd_runtime_resume, NULL)
>>> +};
>>> +
>>> +static struct platform_driver sdhci_sprd_driver = {
>>> + .probe = sdhci_sprd_probe,
>>> + .remove = sdhci_sprd_remove,
>>> + .driver = {
>>> + .name = "sdhci_sprd_r11",
>>> + .of_match_table = of_match_ptr(sdhci_sprd_of_match),
>>> + .pm = &sdhci_sprd_pm_ops,
>>> + },
>>> +};
>>> +module_platform_driver(sdhci_sprd_driver);
>>> +
>>> +MODULE_DESCRIPTION("Spreadtrum sdio host controller r11 driver");
>>> +MODULE_LICENSE("GPL v2");
>>> +MODULE_ALIAS("platform:sdhci-sprd-r11");
>>>
>>
>


2018-07-23 07:13:26

by Chunyan Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 4/7] mmc: sdhci: add 32-bit block count support for v4 mode

On 17 July 2018 at 16:29, Adrian Hunter <[email protected]> wrote:
> On 09/07/18 06:19, Chunyan Zhang wrote:
>> When Host Version 4 is enabled, SDMA System Address register is
>> re-defined as 32-bit Block Count, and SDMA uses ADMA System
>> Address register (05Fh-058h) instead.
>>
>> Signed-off-by: Chunyan Zhang <[email protected]>
>> ---
>> drivers/mmc/host/sdhci.c | 4 +++-
>> drivers/mmc/host/sdhci.h | 1 +
>> 2 files changed, 4 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>> index 7871ae2..f64e766 100644
>> --- a/drivers/mmc/host/sdhci.c
>> +++ b/drivers/mmc/host/sdhci.c
>> @@ -889,6 +889,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
>> static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
>> {
>> u8 ctrl;
>> + u32 reg;
>> struct mmc_data *data = cmd->data;
>>
>> host->data_timeout = 0;
>> @@ -1021,7 +1022,8 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
>> /* Set the DMA boundary value and block size */
>> sdhci_writew(host, SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz),
>> SDHCI_BLOCK_SIZE);
>> - sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
>> + reg = host->v4_mode ? SDHCI_32BIT_BLK_CNT : SDHCI_BLOCK_COUNT;
>> + sdhci_writew(host, data->blocks, reg);
>
> The specification says to set 16-bit block count register to zero when using
> 32-bit block count. It also says it is valid for V4.1 onwards and also for

I found that the 16-bit block count register is zero without being
wrriten by driver, and the weird thing on my board is that the
initialization would be interrupted by SDHCI_INT_ADMA_ERROR if setting
"16-bit block count register" to zero manually. So I will add a check
of whether it is zero already before writing it.


> V4 with SDMA and auto-CMD23.
>
> So maybe we should continue to use the 16-bit block count register with V4.0
>
>> }
>>
>> static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
>> index 24fa58a..889e48b 100644
>> --- a/drivers/mmc/host/sdhci.h
>> +++ b/drivers/mmc/host/sdhci.h
>> @@ -28,6 +28,7 @@
>>
>> #define SDHCI_DMA_ADDRESS 0x00
>> #define SDHCI_ARGUMENT2 SDHCI_DMA_ADDRESS
>> +#define SDHCI_32BIT_BLK_CNT SDHCI_DMA_ADDRESS
>>
>> #define SDHCI_BLOCK_SIZE 0x04
>> #define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
>>
>

2018-07-23 07:13:57

by Chunyan Zhang

[permalink] [raw]
Subject: Re: [PATCH V3 5/7] mmc: sdhci: add Auto CMD Auto Select support

On 17 July 2018 at 19:16, Adrian Hunter <[email protected]> wrote:
> On 09/07/18 06:19, Chunyan Zhang wrote:
>> As SD Host Controller Specification v4.10 documents:
>> Host Controller Version 4.10 defines this "Auto CMD Auto Select" mode.
>> Selection of Auto CMD depends on setting of CMD23 Enable in the Host
>> Control 2 register which indicates whether card supports CMD23. If CMD23
>> Enable =1, Auto CMD23 is used and if CMD23 Enable =0, Auto CMD12 is
>> used. In case of Version 4.10 or later, use of Auto CMD Auto Select is
>> recommended rather than use of Auto CMD12 Enable or Auto CMD23
>> Enable.
>>
>> This patch add this new mode support.
>>
>> Signed-off-by: Chunyan Zhang <[email protected]>
>> ---
>> drivers/mmc/host/sdhci.c | 39 ++++++++++++++++++++++++++++-----------
>> drivers/mmc/host/sdhci.h | 2 ++
>> 2 files changed, 30 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>> index f64e766..1ca3871 100644
>> --- a/drivers/mmc/host/sdhci.c
>> +++ b/drivers/mmc/host/sdhci.c
>> @@ -1033,6 +1033,33 @@ static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
>> !mrq->cap_cmd_during_tfr;
>> }
>>
>> +static inline void sdhci_auto_cmd_enable(struct sdhci_host *host,
>> + struct mmc_command *cmd,
>> + u16 *mode)
>> +{
>> + /*
>> + * In case of Version 4.10 or later, use of 'Auto CMD Auto
>> + * Select' is recommended rather than use of 'Auto CMD12
>> + * Enable' or 'Auto CMD23 Enable'.
>> + */
>> + if (host->version >= SDHCI_SPEC_410) {
>> + *mode |= SDHCI_TRNS_AUTO_SEL;
>
> The rules for selecting auto commands still need to be applied. In effect:
>
> if (sdhci_auto_cmd12(host, cmd->mrq) &&
> (cmd->opcode != SD_IO_RW_EXTENDED)) {
> *mode |= SDHCI_TRNS_AUTO_SEL;
> } else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
> *mode |= SDHCI_TRNS_AUTO_SEL;
> sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);

The fact seems that SDHCI_TRNS_AUTO_SEL mode don't need
SDHCI_ARGUMENT2 register being written. The test result on my board
showed that mounting a blk partition would fail due to lack of a
hardware interrupt if SDHCI_ARGUMENT2 was set like above.

> }
>
> Also the selection of Host Control 2 register CMD23 Enable should be done by
> SDHCI (if supported by the card) not the Spreadtrum driver.

Ok.

>
>> + return;
>> + }
>> +
>> + /*
>> + * If we are sending CMD23, CMD12 never gets sent
>> + * on successful completion (so no Auto-CMD12).
>> + */
>> + if (sdhci_auto_cmd12(host, cmd->mrq) &&
>> + (cmd->opcode != SD_IO_RW_EXTENDED)) {
>> + *mode |= SDHCI_TRNS_AUTO_CMD12;
>> + } else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>> + *mode |= SDHCI_TRNS_AUTO_CMD23;
>> + sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>> + }
>> +}
>> +
>> static void sdhci_set_transfer_mode(struct sdhci_host *host,
>> struct mmc_command *cmd)
>> {
>> @@ -1059,17 +1086,7 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
>>
>> if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
>> mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
>> - /*
>> - * If we are sending CMD23, CMD12 never gets sent
>> - * on successful completion (so no Auto-CMD12).
>> - */
>> - if (sdhci_auto_cmd12(host, cmd->mrq) &&
>> - (cmd->opcode != SD_IO_RW_EXTENDED))
>> - mode |= SDHCI_TRNS_AUTO_CMD12;
>> - else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>> - mode |= SDHCI_TRNS_AUTO_CMD23;
>> - sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>> - }
>> + sdhci_auto_cmd_enable(host, cmd, &mode);
>> }
>>
>> if (data->flags & MMC_DATA_READ)
>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
>> index 889e48b..8263ac6 100644
>> --- a/drivers/mmc/host/sdhci.h
>> +++ b/drivers/mmc/host/sdhci.h
>> @@ -42,6 +42,7 @@
>> #define SDHCI_TRNS_BLK_CNT_EN 0x02
>> #define SDHCI_TRNS_AUTO_CMD12 0x04
>> #define SDHCI_TRNS_AUTO_CMD23 0x08
>> +#define SDHCI_TRNS_AUTO_SEL 0x0C
>> #define SDHCI_TRNS_READ 0x10
>> #define SDHCI_TRNS_MULTI 0x20
>>
>> @@ -185,6 +186,7 @@
>> #define SDHCI_CTRL_DRV_TYPE_D 0x0030
>> #define SDHCI_CTRL_EXEC_TUNING 0x0040
>> #define SDHCI_CTRL_TUNED_CLK 0x0080
>> +#define SDHCI_CMD23_ENABLE 0x0800
>> #define SDHCI_CTRL_V4_MODE 0x1000
>> #define SDHCI_CTRL_64BIT_ADDR 0x2000
>> #define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000
>>
>