Summary
=======
These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
About UHS-II, roughly deal with the following three parts:
1) A UHS-II detection and initialization:
- Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
[2]).
- Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
- In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
Setup Sequence.
2) Send Legacy SD command through SD-TRAN
- Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
Types and Format Overview[3]).
- Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
3) UHS-II Interrupt
- Except for UHS-II error interrupts, most interrupts share the original
interrupt registers.
Patch structure
===============
patch#1-#6: for core
patch#7-#23: for sdhci
patch#24: for GL9755
Changes in v6 (Dec. 12, 2022)
* rebased to the linux-kernel-v6.1.0-rc8 in Ulf Hansson next branch.
* according to the guidance and overall architecture provided
by Ulf Hansson, Ben Chuang and Jason Lai to implement the
UHS-2 Core function based on the patches of the [V4,0/6]
Preparations to support SD UHS-II cards[5].
* according to the guidance and comments provided by
Adrian Hunter, Ben Chuang and AKASHI Takahiro to implement
the UHS-2 Host function based on the patches of the
[RFC,v3.1,00/27] Add support UHS-II for GL9755[4].
* implement the necessary function to let the UHS-2 Core/Host
work properly.
* fix most of checkpatch warnings/errors.
* according to the guidance and comments provided by
Adrian Hunter, Ben Chuang to implement the UHS-2
Host function based on the patches of the
[V5,00/26] Add support UHS-II for GL9755[6].
* The uhs2_post_attach_sd() has implemented in Patch#6 and
Patch#17 so drop the V5 version of the Patch#23.
* Modifies the usage of the flags used by the sdhci host for
MMC_UHS2_INITIALIZED.
* Patch#5: Drop unused definitions and functions.
* Patch#7: Rename definitions.
Use BIT() GENMASK() in some cases.
* Patch#8: Merge V5 version of Patch[7] and Patch[9] into
V6 version of Patch[8].
* Patch#9: Drop unnecessary function.
Rename used definitions.
* Patch#10: Drop unnecessary function and simplify some code.
* Patch#11: Drop unnecessary function.
Add new mmc_opt_regulator_set_ocr function.
* Patch#13: Drop unnecessary function.
Use GENMASK() and FIELD_PREP() in some cases.
* Patch#14: Drop unnecessary function.
Modify return value in some function.
Use GENMASK() and FIELD_PREP() in some cases.
* Patch#15: Drop unnecessary function.
Rename used definitions.
Use GENMASK() and FIELD_GET() in some cases.
Wrap at 100 columns in some functions.
* Patch#16: Drop unnecessary function.
* Patch#17: Drop unnecessary function.
Drop the unnecessary parameter when call the DBG()
function.
Rename used definitions.
Cancel the export state of some functions.
Use GENMASK() and FIELD_PREP() in some cases.
* Patch#18: Drop unnecessary function.
Add uhs2_dev_cmd function to simplify some functions.
Rename used definitions.
Cancel the export state of some functions.
Use GENMASK() and FIELD_PREP() in some cases.
* Patch#19: Drop unnecessary function.
Add sdhci_uhs2_mode() in some functions.
Rename used definitions.
Cancel the export state of some functions.
* Patch#20: Add new complete_work_fn/thread_irq_fn variables in
struct sdhci_host.
Use complete_work_fn/thread_irq_fn variables in
sdhci_alloc_host()/sdhci_uhs2_add_host().
Rename used definitions.
* Patch[24]: Rename used definitions.
Reference
=========
[1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
[2] SD Host Controller Simplified Specification 4.20
[3] UHS-II Simplified Addendum 1.02
[4] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
[5] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
[6] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
----------------- original cover letter from v5 -----------------
Summary
=======
These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
About UHS-II, roughly deal with the following three parts:
1) A UHS-II detection and initialization:
- Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
[2]).
- Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
- In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
Setup Sequence.
2) Send Legacy SD command through SD-TRAN
- Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
Types and Format Overview[3]).
- Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
3) UHS-II Interrupt
- Except for UHS-II error interrupts, most interrupts share the original
interrupt registers.
Patch structure
===============
patch#1-#6: for core
patch#7-#25: for sdhci
patch#26: for GL9755
Changes in v5 (Oct. 19, 2022)
* rebased to the linux-kernel-v6.1-rc1 in Ulf Hansson next branch.
* according to the guidance and overall architecture provided
by Ulf Hansson, Ben Chuang and Jason Lai to implement the
UHS-2 Core function based on the patches of the [V4,0/6]
Preparations to support SD UHS-II cards[5].
* according to the guidance and comments provided by
Adrian Hunter, Ben Chuang and AKASHI Takahiro to implement
the UHS-2 Host function based on the patches of the
[RFC,v3.1,00/27] Add support UHS-II for GL9755[4].
* implement the necessary function to let the UHS-2 Core/Host
work properly.
* fix most of checkpatch warnings/errors
Reference
=========
[1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
[2] SD Host Controller Simplified Specification 4.20
[3] UHS-II Simplified Addendum 1.02
[4] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
[5] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
----------------- original cover letter from v3.1 -----------------
This is an interim snapshot of our next version, v4, for enabling
UHS-II on MMC/SD.
It is focused on 'sdhci' side to address Adrian's comments regarding
"modularising" sdhci-uhs2.c.
The whole aim of this version is to get early feedback from Adrian (and
others) on this issue. Without any consensus about the code structure,
it would make little sense to go further ahead on sdhci side.
(Actually, Adrian has made no comments other than "modularising" so far.)
I heavily reworked/refactored sdhci-uhs2.c and re-organised the patch
set to meet what I believe Adrian expects; no UHS-II related code in
Legacy (UHS-I) code or sdhci.c.
Nevertheless, almost of all changes I made are trivial and straightforward
in this direction, and I believe that there is no logic changed since v3
except sdhci_uhs2_irq(), as ops->irq hook, where we must deal with UHS-II
command sequences in addition to UHS-II errors. So I added extra handlings.
I admit that there is plenty of room for improvements (for example,
handling host->flags), but again the focal point here is how sdhci-uhs2.c
should be built as a module.
Please review this series (particularly Patch#8-#26 and #27) from this
viewpoint in the first place.
(Ben is working on 'host' side but there is no change on 'host' side
in this submission except a minor tweak.)
Thanks,
-Takahiro Akashi
------ original cover letter from v3 ------
Summary
=======
These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
About UHS-II, roughly deal with the following three parts:
1) A UHS-II detection and initialization:
- Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
[2]).
- Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
- In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
Setup Sequence.
2) Send Legacy SD command through SD-TRAN
- Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
Types and Format Overview[3]).
- Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
3) UHS-II Interrupt
- Except for UHS-II error interrupts, most interrupts share the original
interrupt registers.
Patch structure
===============
patch#1-#7: for core
patch#8-#17: for sdhci
patch#18-#21: for GL9755
Tests
=====
Ran 'dd' command to evaluate the performance:
(SanDisk UHS-II card on GL9755 controller)
Read Write
UHS-II disabled (UHS-I): 88.3MB/s 60.7MB/s
UHS-II enabled : 206MB/s 80MB/s
TODO
====
- replace some define with BIT macro
Reference
=========
[1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
[2] SD Host Controller Simplified Specification 4.20
[3] UHS-II Simplified Addendum 1.02
Changes in v3 (Jul. 10, 2020)
* rebased to v5.8-rc4
* add copyright notice
* reorganize the patch set and split some commits into smaller ones
* separate uhs-2 headers from others
* correct wrong spellings
* fix most of checkpatch warnings/errors
* remove all k[cz]alloc() from the code
* guard sdhci-uhs2 specific code with
'if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2))'
* make sdhci-uhs2.c as a module
* trivial changes, including
- rename back sdhci-core.c to sdhci.c
- allow vendor code to disable uhs2 if v4_mode == 0
in __sdhci_add_host()
- merge uhs2_power_up() into mmc_power_up()
- remove flag_uhs2 from mmc_attach_sd()
- add function descriptions to EXPORT'ed functions
- other minor code optimization
Changes in v2 (Jan. 9, 2020)
* rebased to v5.5-rc5
AKASHI Takahiro (1):
mmc: sdhci-pci: add UHS-II support framework
Ben Chuang (1):
mmc: sdhci-uhs2: add post-mmc_attach_sd hook
Ulf Hansson (3):
mmc: core: Prepare to support SD UHS-II cards
mmc: core: Announce successful insertion of an SD UHS-II card
mmc: core: Extend support for mmc regulators with a vqmmc2
Victor Shih (19):
mmc: core: Cleanup printing of speed mode at card insertion
mmc: core: Add definitions for SD UHS-II cards
mmc: core: Support UHS-II card control and access
mmc: sdhci: add UHS-II related definitions in headers
mmc: sdhci: add UHS-II module and add a kernel configuration
mmc: sdhci-uhs2: dump UHS-II registers
mmc: sdhci-uhs2: add reset function and uhs2_mode function
mmc: sdhci-uhs2: add set_power() to support vdd2
mmc: sdhci-uhs2: skip signal_voltage_switch()
mmc: sdhci-uhs2: add set_timeout()
mmc: sdhci-uhs2: add set_ios()
mmc: sdhci-uhs2: add detect_init() to detect the interface
mmc: sdhci-uhs2: add clock operations
mmc: sdhci-uhs2: add uhs2_control() to initialise the interface
mmc: sdhci-uhs2: add request() and others
mmc: sdhci-uhs2: add irq() and others
mmc: sdhci-uhs2: add add_host() and others to set up the driver
mmc: sdhci-uhs2: add pre-detect_init hook
mmc: sdhci-pci-gli: enable UHS-II mode for GL9755
drivers/mmc/core/Makefile | 2 +-
drivers/mmc/core/block.c | 6 +-
drivers/mmc/core/bus.c | 37 +-
drivers/mmc/core/core.c | 37 +-
drivers/mmc/core/core.h | 1 +
drivers/mmc/core/host.h | 4 +
drivers/mmc/core/mmc_ops.c | 25 +-
drivers/mmc/core/mmc_ops.h | 1 +
drivers/mmc/core/regulator.c | 34 +
drivers/mmc/core/sd.c | 11 +-
drivers/mmc/core/sd.h | 3 +
drivers/mmc/core/sd_ops.c | 13 +
drivers/mmc/core/sd_ops.h | 3 +
drivers/mmc/core/sd_uhs2.c | 1400 ++++++++++++++++++++++++++++
drivers/mmc/host/Kconfig | 10 +
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/sdhci-pci-core.c | 16 +-
drivers/mmc/host/sdhci-pci-gli.c | 309 ++++++-
drivers/mmc/host/sdhci-pci.h | 3 +
drivers/mmc/host/sdhci-uhs2.c | 1414 +++++++++++++++++++++++++++++
drivers/mmc/host/sdhci-uhs2.h | 188 ++++
drivers/mmc/host/sdhci.c | 343 ++++---
drivers/mmc/host/sdhci.h | 100 +-
include/linux/mmc/card.h | 47 +
include/linux/mmc/core.h | 13 +
include/linux/mmc/host.h | 91 ++
include/linux/mmc/sd_uhs2.h | 263 ++++++
27 files changed, 4205 insertions(+), 170 deletions(-)
create mode 100644 drivers/mmc/core/sd_uhs2.c
create mode 100644 drivers/mmc/host/sdhci-uhs2.c
create mode 100644 drivers/mmc/host/sdhci-uhs2.h
create mode 100644 include/linux/mmc/sd_uhs2.h
--
2.25.1
From: Ulf Hansson <[email protected]>
Updates in V4:
- Make mmc_card_uhs2() take struct mmc_host* as in-param.
Update in previous version:
To inform the users about SD UHS-II cards, let's extend the print at card
insertion with a "UHS-II" substring. Within this change, it seems
reasonable to convert from using "ultra high speed" into "UHS-I speed", for
the UHS-I type, as it should makes it more clear.
Note that, the new print for UHS-II cards doesn't include the actual
selected speed mode. Instead, this is going to be added from subsequent
change.
Signed-off-by: Ulf Hansson <[email protected]>
---
drivers/mmc/core/bus.c | 4 +++-
drivers/mmc/core/host.h | 4 ++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c
index bbbbdbca5366..c8d51fc7d5bb 100644
--- a/drivers/mmc/core/bus.c
+++ b/drivers/mmc/core/bus.c
@@ -341,7 +341,9 @@ int mmc_add_card(struct mmc_card *card)
if (mmc_card_hs(card))
speed_mode = "high speed ";
else if (mmc_card_uhs(card))
- speed_mode = "ultra high speed ";
+ speed_mode = "UHS-I speed ";
+ else if (mmc_card_uhs2(card->host))
+ speed_mode = "UHS-II speed ";
else if (mmc_card_ddr52(card))
speed_mode = "high speed DDR ";
else if (mmc_card_hs200(card))
diff --git a/drivers/mmc/core/host.h b/drivers/mmc/core/host.h
index 48c4952512a5..ba6a80e9b360 100644
--- a/drivers/mmc/core/host.h
+++ b/drivers/mmc/core/host.h
@@ -89,5 +89,9 @@ static inline bool mmc_card_sd_express(struct mmc_host *host)
host->ios.timing == MMC_TIMING_SD_EXP_1_2V;
}
+static inline bool mmc_card_uhs2(struct mmc_host *host)
+{
+ return host->ios.timing == MMC_TIMING_SD_UHS2;
+}
#endif
--
2.25.1
This is a UHS-II version of sdhci's set_timeout() operation.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 72 +++++++++++++++++++++++++++++++++++
drivers/mmc/host/sdhci-uhs2.h | 1 +
2 files changed, 73 insertions(+)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 2342c05c4cdb..00b1b69b49ea 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -13,6 +13,7 @@
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/iopoll.h>
+#include <linux/bitfield.h>
#include "sdhci.h"
#include "sdhci-uhs2.h"
@@ -141,6 +142,77 @@ static void sdhci_uhs2_set_power(struct sdhci_host *host, unsigned char mode,
}
}
+static u8 sdhci_calc_timeout_uhs2(struct sdhci_host *host, u8 *cmd_res, u8 *dead_lock)
+{
+ u8 count;
+ unsigned int cmd_res_timeout, dead_lock_timeout, current_timeout;
+ /* timeout in us */
+ cmd_res_timeout = 5 * 1000;
+ dead_lock_timeout = 1 * 1000 * 1000;
+
+ /*
+ * Figure out needed cycles.
+ * We do this in steps in order to fit inside a 32 bit int.
+ * The first step is the minimum timeout, which will have a
+ * minimum resolution of 6 bits:
+ * (1) 2^13*1000 > 2^22,
+ * (2) host->timeout_clk < 2^16
+ * =>
+ * (1) / (2) > 2^6
+ */
+ count = 0;
+ current_timeout = (1 << 13) * 1000 / host->timeout_clk;
+ while (current_timeout < cmd_res_timeout) {
+ count++;
+ current_timeout <<= 1;
+ if (count >= 0xF)
+ break;
+ }
+
+ if (count >= 0xF) {
+ DBG("%s: Too large timeout 0x%x requested for CMD_RES!\n",
+ mmc_hostname(host->mmc), count);
+ count = 0xE;
+ }
+ *cmd_res = count;
+
+ count = 0;
+ current_timeout = (1 << 13) * 1000 / host->timeout_clk;
+ while (current_timeout < dead_lock_timeout) {
+ count++;
+ current_timeout <<= 1;
+ if (count >= 0xF)
+ break;
+ }
+
+ if (count >= 0xF) {
+ DBG("%s: Too large timeout 0x%x requested for DEADLOCK!\n",
+ mmc_hostname(host->mmc), count);
+ count = 0xE;
+ }
+ *dead_lock = count;
+
+ return count;
+}
+
+static void __sdhci_uhs2_set_timeout(struct sdhci_host *host)
+{
+ u8 cmd_res, dead_lock;
+
+ sdhci_calc_timeout_uhs2(host, &cmd_res, &dead_lock);
+ cmd_res |= FIELD_PREP(SDHCI_UHS2_TIMER_CTRL_DEADLOCK_MASK, dead_lock);
+ sdhci_writeb(host, cmd_res, SDHCI_UHS2_TIMER_CTRL);
+}
+
+void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
+{
+ __sdhci_set_timeout(host, cmd);
+
+ if (sdhci_uhs2_mode(host))
+ __sdhci_uhs2_set_timeout(host);
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_set_timeout);
+
/*****************************************************************************\
* *
* MMC callbacks *
diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
index 03b9f6675cdc..a58ef19c08aa 100644
--- a/drivers/mmc/host/sdhci-uhs2.h
+++ b/drivers/mmc/host/sdhci-uhs2.h
@@ -177,5 +177,6 @@ struct sdhci_host;
void sdhci_uhs2_dump_regs(struct sdhci_host *host);
bool sdhci_uhs2_mode(struct sdhci_host *host);
void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
+void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
#endif /* __SDHCI_UHS2_H */
--
2.25.1
This is a UHS-II version of sdhci's request() operation.
It handles UHS-II related command interrupts and errors.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 223 +++++++++++++++++++++++++++++++++-
drivers/mmc/host/sdhci-uhs2.h | 3 +
drivers/mmc/host/sdhci.c | 111 +++++++++--------
drivers/mmc/host/sdhci.h | 6 +
4 files changed, 294 insertions(+), 49 deletions(-)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 7a34f66c4215..769cf88b21cf 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -16,6 +16,7 @@
#include <linux/bitfield.h>
#include <linux/ktime.h>
#include <linux/mmc/mmc.h>
+#include <linux/dmaengine.h>
#include "sdhci.h"
#include "sdhci-uhs2.h"
@@ -794,6 +795,226 @@ static void sdhci_uhs2_finish_command(struct sdhci_host *host)
__sdhci_finish_mrq(host, cmd->mrq);
}
+/*****************************************************************************\
+ * *
+ * Request done *
+ * *
+\*****************************************************************************/
+
+static bool sdhci_uhs2_request_done(struct sdhci_host *host)
+{
+ unsigned long flags;
+ struct mmc_request *mrq;
+ int i;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ for (i = 0; i < SDHCI_MAX_MRQS; i++) {
+ mrq = host->mrqs_done[i];
+ if (mrq)
+ break;
+ }
+
+ if (!mrq) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return true;
+ }
+
+ /*
+ * Always unmap the data buffers if they were mapped by
+ * sdhci_prepare_data() whenever we finish with a request.
+ * This avoids leaking DMA mappings on error.
+ */
+ if (host->flags & SDHCI_REQ_USE_DMA)
+ sdhci_request_done_dma(host, mrq);
+
+ /*
+ * The controller needs a reset of internal state machines
+ * upon error conditions.
+ */
+ if (sdhci_needs_reset(host, mrq)) {
+ /*
+ * Do not finish until command and data lines are available for
+ * reset. Note there can only be one other mrq, so it cannot
+ * also be in mrqs_done, otherwise host->cmd and host->data_cmd
+ * would both be null.
+ */
+ if (host->cmd || host->data_cmd) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return true;
+ }
+
+ /* Some controllers need this kick or reset won't work here */
+ if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET)
+ /* This is to force an update */
+ host->ops->set_clock(host, host->clock);
+
+ sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
+ host->pending_reset = false;
+ }
+
+ host->mrqs_done[i] = NULL;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (host->ops->request_done)
+ host->ops->request_done(host, mrq);
+ else
+ mmc_request_done(host->mmc, mrq);
+
+ return false;
+}
+
+static void sdhci_uhs2_complete_work(struct work_struct *work)
+{
+ struct sdhci_host *host = container_of(work, struct sdhci_host,
+ complete_work);
+
+ if (!sdhci_uhs2_mode(host)) {
+ sdhci_complete_work(work);
+ return;
+ }
+
+ while (!sdhci_uhs2_request_done(host))
+ ;
+}
+
+/*****************************************************************************\
+ * *
+ * Interrupt handling *
+ * *
+\*****************************************************************************/
+
+static void __sdhci_uhs2_irq(struct sdhci_host *host, u32 uhs2mask)
+{
+ struct mmc_command *cmd = host->cmd;
+
+ DBG("*** %s got UHS2 error interrupt: 0x%08x\n",
+ mmc_hostname(host->mmc), uhs2mask);
+
+ if (uhs2mask & SDHCI_UHS2_INT_CMD_ERR_MASK) {
+ if (!host->cmd) {
+ pr_err("%s: Got cmd interrupt 0x%08x but no cmd.\n",
+ mmc_hostname(host->mmc),
+ (unsigned int)uhs2mask);
+ sdhci_dumpregs(host);
+ return;
+ }
+ host->cmd->error = -EILSEQ;
+ if (uhs2mask & SDHCI_UHS2_INT_CMD_TIMEOUT)
+ host->cmd->error = -ETIMEDOUT;
+ }
+
+ if (uhs2mask & SDHCI_UHS2_INT_DATA_ERR_MASK) {
+ if (!host->data) {
+ pr_err("%s: Got data interrupt 0x%08x but no data.\n",
+ mmc_hostname(host->mmc),
+ (unsigned int)uhs2mask);
+ sdhci_dumpregs(host);
+ return;
+ }
+
+ if (uhs2mask & SDHCI_UHS2_INT_DEADLOCK_TIMEOUT) {
+ pr_err("%s: Got deadlock timeout interrupt 0x%08x\n",
+ mmc_hostname(host->mmc),
+ (unsigned int)uhs2mask);
+ host->data->error = -ETIMEDOUT;
+ } else if (uhs2mask & SDHCI_UHS2_INT_ADMA_ERROR) {
+ pr_err("%s: ADMA error = 0x %x\n",
+ mmc_hostname(host->mmc),
+ sdhci_readb(host, SDHCI_ADMA_ERROR));
+ host->data->error = -EIO;
+ } else {
+ host->data->error = -EILSEQ;
+ }
+ }
+
+ if (host->data && host->data->error)
+ sdhci_uhs2_finish_data(host);
+ else
+ sdhci_finish_mrq(host, cmd->mrq);
+}
+
+u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask)
+{
+ u32 mask = intmask, uhs2mask;
+
+ if (!(host->mmc->flags & MMC_UHS2_SUPPORT))
+ goto out;
+
+ if (intmask & SDHCI_INT_ERROR) {
+ uhs2mask = sdhci_readl(host, SDHCI_UHS2_INT_STATUS);
+ if (!(uhs2mask & SDHCI_UHS2_INT_ERROR_MASK))
+ goto cmd_irq;
+
+ /* Clear error interrupts */
+ sdhci_writel(host, uhs2mask & SDHCI_UHS2_INT_ERROR_MASK,
+ SDHCI_UHS2_INT_STATUS);
+
+ /* Handle error interrupts */
+ __sdhci_uhs2_irq(host, uhs2mask);
+
+ /* Caller, shdci_irq(), doesn't have to care UHS-2 errors */
+ intmask &= ~SDHCI_INT_ERROR;
+ mask &= SDHCI_INT_ERROR;
+ }
+
+cmd_irq:
+ if (intmask & SDHCI_INT_CMD_MASK) {
+ /* Clear command interrupt */
+ sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK, SDHCI_INT_STATUS);
+
+ /* Handle command interrupt */
+ if (intmask & SDHCI_INT_RESPONSE)
+ sdhci_uhs2_finish_command(host);
+
+ /* Caller, shdci_irq(), doesn't have to care UHS-2 command */
+ intmask &= ~SDHCI_INT_CMD_MASK;
+ mask &= SDHCI_INT_CMD_MASK;
+ }
+
+ /* Clear already-handled interrupts. */
+ sdhci_writel(host, mask, SDHCI_INT_STATUS);
+
+out:
+ return intmask;
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_irq);
+
+static irqreturn_t sdhci_uhs2_thread_irq(int irq, void *dev_id)
+{
+ struct sdhci_host *host = dev_id;
+ struct mmc_command *cmd;
+ unsigned long flags;
+ u32 isr;
+
+ if (!sdhci_uhs2_mode(host))
+ return sdhci_thread_irq(irq, dev_id);
+
+ while (!sdhci_uhs2_request_done(host))
+ ;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ isr = host->thread_isr;
+ host->thread_isr = 0;
+
+ cmd = host->deferred_cmd;
+ if (cmd && !sdhci_uhs2_send_command_retry(host, cmd, flags))
+ sdhci_finish_mrq(host, cmd->mrq);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (isr & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
+ struct mmc_host *mmc = host->mmc;
+
+ mmc->ops->card_event(mmc);
+ mmc_detect_change(mmc, msecs_to_jiffies(200));
+ }
+
+ return IRQ_HANDLED;
+}
+
void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host = mmc_priv(mmc);
@@ -978,7 +1199,7 @@ static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc)
}
/* Init complete, do soft reset and enable UHS2 error irqs. */
- host->ops->uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
+ sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
/*
* N.B SDHCI_INT_ENABLE and SDHCI_SIGNAL_ENABLE was cleared
diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
index 184fee80253c..64b7f356c9fc 100644
--- a/drivers/mmc/host/sdhci-uhs2.h
+++ b/drivers/mmc/host/sdhci-uhs2.h
@@ -179,5 +179,8 @@ bool sdhci_uhs2_mode(struct sdhci_host *host);
void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
+void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq);
+int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq);
+u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask);
#endif /* __SDHCI_UHS2_H */
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index c547fc3b8b47..6c67b79102eb 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -47,6 +47,8 @@
static unsigned int debug_quirks = 0;
static unsigned int debug_quirks2;
+static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
+
void sdhci_dumpregs(struct sdhci_host *host)
{
SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n");
@@ -1256,11 +1258,12 @@ static int sdhci_external_dma_init(struct sdhci_host *host)
return ret;
}
-static struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
- struct mmc_data *data)
+struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
+ struct mmc_data *data)
{
return data->flags & MMC_DATA_WRITE ? host->tx_chan : host->rx_chan;
}
+EXPORT_SYMBOL_GPL(sdhci_external_dma_channel);
int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd)
{
@@ -1509,7 +1512,7 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
}
-static bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
+bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
{
return (!(host->flags & SDHCI_DEVICE_DEAD) &&
((mrq->cmd && mrq->cmd->error) ||
@@ -1517,8 +1520,9 @@ static bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
(mrq->data && mrq->data->stop && mrq->data->stop->error) ||
(host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)));
}
+EXPORT_SYMBOL_GPL(sdhci_needs_reset);
-static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
+void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
{
int i;
@@ -1538,6 +1542,7 @@ static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
WARN_ON(i >= SDHCI_MAX_MRQS);
}
+EXPORT_SYMBOL_GPL(sdhci_set_mrq_done);
void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
{
@@ -3122,6 +3127,55 @@ static const struct mmc_host_ops sdhci_ops = {
* *
\*****************************************************************************/
+void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq)
+{
+ struct mmc_data *data = mrq->data;
+
+ if (data && data->host_cookie == COOKIE_MAPPED) {
+ if (host->bounce_buffer) {
+ /*
+ * On reads, copy the bounced data into the
+ * sglist
+ */
+ if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
+ unsigned int length = data->bytes_xfered;
+
+ if (length > host->bounce_buffer_size) {
+ pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
+ mmc_hostname(host->mmc),
+ host->bounce_buffer_size,
+ data->bytes_xfered);
+ /* Cap it down and continue */
+ length = host->bounce_buffer_size;
+ }
+ dma_sync_single_for_cpu(
+ host->mmc->parent,
+ host->bounce_addr,
+ host->bounce_buffer_size,
+ DMA_FROM_DEVICE);
+ sg_copy_from_buffer(data->sg,
+ data->sg_len,
+ host->bounce_buffer,
+ length);
+ } else {
+ /* No copying, just switch ownership */
+ dma_sync_single_for_cpu(
+ host->mmc->parent,
+ host->bounce_addr,
+ host->bounce_buffer_size,
+ mmc_get_dma_dir(data));
+ }
+ } else {
+ /* Unmap the raw data */
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len,
+ mmc_get_dma_dir(data));
+ }
+ data->host_cookie = COOKIE_UNMAPPED;
+ }
+}
+EXPORT_SYMBOL_GPL(sdhci_request_done_dma);
+
static bool sdhci_request_done(struct sdhci_host *host)
{
unsigned long flags;
@@ -3186,48 +3240,7 @@ static bool sdhci_request_done(struct sdhci_host *host)
sdhci_set_mrq_done(host, mrq);
}
- if (data && data->host_cookie == COOKIE_MAPPED) {
- if (host->bounce_buffer) {
- /*
- * On reads, copy the bounced data into the
- * sglist
- */
- if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
- unsigned int length = data->bytes_xfered;
-
- if (length > host->bounce_buffer_size) {
- pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
- mmc_hostname(host->mmc),
- host->bounce_buffer_size,
- data->bytes_xfered);
- /* Cap it down and continue */
- length = host->bounce_buffer_size;
- }
- dma_sync_single_for_cpu(
- mmc_dev(host->mmc),
- host->bounce_addr,
- host->bounce_buffer_size,
- DMA_FROM_DEVICE);
- sg_copy_from_buffer(data->sg,
- data->sg_len,
- host->bounce_buffer,
- length);
- } else {
- /* No copying, just switch ownership */
- dma_sync_single_for_cpu(
- mmc_dev(host->mmc),
- host->bounce_addr,
- host->bounce_buffer_size,
- mmc_get_dma_dir(data));
- }
- } else {
- /* Unmap the raw data */
- dma_unmap_sg(mmc_dev(host->mmc), data->sg,
- data->sg_len,
- mmc_get_dma_dir(data));
- }
- data->host_cookie = COOKIE_UNMAPPED;
- }
+ sdhci_request_done_dma(host, mrq);
}
host->mrqs_done[i] = NULL;
@@ -3242,7 +3255,7 @@ static bool sdhci_request_done(struct sdhci_host *host)
return false;
}
-static void sdhci_complete_work(struct work_struct *work)
+void sdhci_complete_work(struct work_struct *work)
{
struct sdhci_host *host = container_of(work, struct sdhci_host,
complete_work);
@@ -3250,6 +3263,7 @@ static void sdhci_complete_work(struct work_struct *work)
while (!sdhci_request_done(host))
;
}
+EXPORT_SYMBOL_GPL(sdhci_complete_work);
static void sdhci_timeout_timer(struct timer_list *t)
{
@@ -3705,7 +3719,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
return result;
}
-static irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
+irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
{
struct sdhci_host *host = dev_id;
struct mmc_command *cmd;
@@ -3735,6 +3749,7 @@ static irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
return IRQ_HANDLED;
}
+EXPORT_SYMBOL_GPL(sdhci_thread_irq);
/*****************************************************************************\
* *
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 3b87fcae1294..4979af7cabca 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -840,7 +840,10 @@ int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd);
void sdhci_external_dma_release(struct sdhci_host *host);
void __sdhci_external_dma_prepare_data(struct sdhci_host *host, struct mmc_command *cmd);
void sdhci_external_dma_pre_transfer(struct sdhci_host *host, struct mmc_command *cmd);
+struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host, struct mmc_data *data);
#endif
+bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq);
+void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq);
void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
void __sdhci_finish_data_common(struct sdhci_host *host);
@@ -870,6 +873,9 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios);
void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
+void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq);
+void sdhci_complete_work(struct work_struct *work);
+irqreturn_t sdhci_thread_irq(int irq, void *dev_id);
void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
dma_addr_t addr, int len, unsigned int cmd);
--
2.25.1
This is a sdhci version of mmc's request operation.
It covers both UHS-I and UHS-II.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 418 ++++++++++++++++++++++++++++++++++
drivers/mmc/host/sdhci.c | 78 ++++---
drivers/mmc/host/sdhci.h | 14 ++
3 files changed, 481 insertions(+), 29 deletions(-)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index de15a6559702..7a34f66c4215 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -15,6 +15,7 @@
#include <linux/iopoll.h>
#include <linux/bitfield.h>
#include <linux/ktime.h>
+#include <linux/mmc/mmc.h>
#include "sdhci.h"
#include "sdhci-uhs2.h"
@@ -25,6 +26,8 @@
#define SDHCI_UHS2_DUMP(f, x...) \
pr_err("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x)
+#define UHS2_ARG_IOADR_MASK 0xfff
+
void sdhci_uhs2_dump_regs(struct sdhci_host *host)
{
if (!(host->mmc->flags & MMC_UHS2_SUPPORT))
@@ -59,6 +62,11 @@ EXPORT_SYMBOL_GPL(sdhci_uhs2_dump_regs);
* *
\*****************************************************************************/
+static inline u16 uhs2_dev_cmd(struct mmc_command *cmd)
+{
+ return be16_to_cpu((__be16)cmd->uhs2_cmd->arg) & UHS2_ARG_IOADR_MASK;
+}
+
static inline int mmc_opt_regulator_set_ocr(struct mmc_host *mmc,
struct regulator *supply,
unsigned short vdd_bit)
@@ -452,6 +460,415 @@ static int sdhci_uhs2_control(struct mmc_host *mmc, enum sd_uhs2_operation op)
return err;
}
+/*****************************************************************************\
+ * *
+ * Core functions *
+ * *
+\*****************************************************************************/
+
+static void sdhci_uhs2_prepare_data(struct sdhci_host *host,
+ struct mmc_command *cmd)
+{
+ struct mmc_data *data = cmd->data;
+
+ sdhci_initialize_data(host, data);
+
+ sdhci_prepare_dma(host, data);
+
+ sdhci_writew(host, data->blksz, SDHCI_UHS2_BLOCK_SIZE);
+ sdhci_writew(host, data->blocks, SDHCI_UHS2_BLOCK_COUNT);
+}
+
+static void sdhci_uhs2_finish_data(struct sdhci_host *host)
+{
+ struct mmc_data *data = host->data;
+
+ __sdhci_finish_data_common(host);
+
+ __sdhci_finish_mrq(host, data->mrq);
+}
+
+static void sdhci_uhs2_set_transfer_mode(struct sdhci_host *host,
+ struct mmc_command *cmd)
+{
+ u16 mode;
+ struct mmc_data *data = cmd->data;
+
+ if (!data) {
+ /* clear Auto CMD settings for no data CMDs */
+ if (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_TRANS_ABORT) {
+ mode = 0;
+ } else {
+ mode = sdhci_readw(host, SDHCI_UHS2_TRANS_MODE);
+ if (cmd->opcode == MMC_STOP_TRANSMISSION || cmd->opcode == MMC_ERASE)
+ mode |= SDHCI_UHS2_TRNS_WAIT_EBSY;
+ else
+ /* send status mode */
+ if (cmd->opcode == MMC_SEND_STATUS)
+ mode = 0;
+ }
+
+ DBG("UHS2 no data trans mode is 0x%x.\n", mode);
+
+ sdhci_writew(host, mode, SDHCI_UHS2_TRANS_MODE);
+ return;
+ }
+
+ WARN_ON(!host->data);
+
+ mode = SDHCI_UHS2_TRNS_BLK_CNT_EN | SDHCI_UHS2_TRNS_WAIT_EBSY;
+ if (data->flags & MMC_DATA_WRITE)
+ mode |= SDHCI_UHS2_TRNS_DATA_TRNS_WRT;
+
+ if (data->blocks == 1 &&
+ data->blksz != 512 &&
+ cmd->opcode != MMC_READ_SINGLE_BLOCK &&
+ cmd->opcode != MMC_WRITE_BLOCK) {
+ mode &= ~SDHCI_UHS2_TRNS_BLK_CNT_EN;
+ mode |= SDHCI_UHS2_TRNS_BLK_BYTE_MODE;
+ }
+
+ if (host->flags & SDHCI_REQ_USE_DMA)
+ mode |= SDHCI_UHS2_TRNS_DMA;
+
+ if ((host->mmc->uhs2_ios.is_2L_HD_mode) && !cmd->uhs2_tmode0_flag)
+ mode |= SDHCI_UHS2_TRNS_2L_HD;
+
+ sdhci_writew(host, mode, SDHCI_UHS2_TRANS_MODE);
+
+ DBG("UHS2 trans mode is 0x%x.\n", mode);
+}
+
+static void __sdhci_uhs2_send_command(struct sdhci_host *host,
+ struct mmc_command *cmd)
+{
+ int i, j;
+ int cmd_reg;
+
+ i = 0;
+ sdhci_writel(host,
+ ((u32)cmd->uhs2_cmd->arg << 16) |
+ (u32)cmd->uhs2_cmd->header,
+ SDHCI_UHS2_CMD_PACKET + i);
+ i += 4;
+
+ /*
+ * Per spec, playload (config) should be MSB before sending out.
+ * But we don't need convert here because had set payload as
+ * MSB when preparing config read/write commands.
+ */
+ for (j = 0; j < cmd->uhs2_cmd->payload_len / sizeof(u32); j++) {
+ sdhci_writel(host, *(cmd->uhs2_cmd->payload + j), SDHCI_UHS2_CMD_PACKET + i);
+ i += 4;
+ }
+
+ for ( ; i < SDHCI_UHS2_CMD_PACK_MAX_LEN; i += 4)
+ sdhci_writel(host, 0, SDHCI_UHS2_CMD_PACKET + i);
+
+ DBG("UHS2 CMD packet_len = %d.\n", cmd->uhs2_cmd->packet_len);
+ for (i = 0; i < cmd->uhs2_cmd->packet_len; i++)
+ DBG("UHS2 CMD_PACKET[%d] = 0x%x.\n", i,
+ sdhci_readb(host, SDHCI_UHS2_CMD_PACKET + i));
+
+ cmd_reg = FIELD_PREP(SDHCI_UHS2_CMD_PACK_LEN_MASK, cmd->uhs2_cmd->packet_len);
+ if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC)
+ cmd_reg |= SDHCI_UHS2_CMD_DATA;
+ if (cmd->opcode == MMC_STOP_TRANSMISSION)
+ cmd_reg |= SDHCI_UHS2_CMD_CMD12;
+
+ /* UHS2 Native ABORT */
+ if ((cmd->uhs2_cmd->header & UHS2_NATIVE_PACKET) &&
+ (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_TRANS_ABORT))
+ cmd_reg |= SDHCI_UHS2_CMD_TRNS_ABORT;
+
+ /* UHS2 Native DORMANT */
+ if ((cmd->uhs2_cmd->header & UHS2_NATIVE_PACKET) &&
+ (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_GO_DORMANT_STATE))
+ cmd_reg |= SDHCI_UHS2_CMD_DORMANT;
+
+ DBG("0x%x is set to UHS2 CMD register.\n", cmd_reg);
+
+ sdhci_writew(host, cmd_reg, SDHCI_UHS2_CMD);
+}
+
+static bool sdhci_uhs2_send_command(struct sdhci_host *host,
+ struct mmc_command *cmd)
+{
+ int flags;
+ u32 mask;
+ unsigned long timeout;
+
+ WARN_ON(host->cmd);
+
+ /* Initially, a command has no error */
+ cmd->error = 0;
+
+ if (cmd->opcode == MMC_STOP_TRANSMISSION)
+ cmd->flags |= MMC_RSP_BUSY;
+
+ mask = SDHCI_CMD_INHIBIT;
+
+ if (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask)
+ return false;
+
+ host->cmd = cmd;
+ host->data_timeout = 0;
+ if (sdhci_data_line_cmd(cmd)) {
+ WARN_ON(host->data_cmd);
+ host->data_cmd = cmd;
+ __sdhci_uhs2_set_timeout(host);
+ }
+
+ if (cmd->data)
+ sdhci_uhs2_prepare_data(host, cmd);
+
+ sdhci_uhs2_set_transfer_mode(host, cmd);
+
+ if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
+ WARN_ONCE(1, "Unsupported response type!\n");
+ /*
+ * This does not happen in practice because 136-bit response
+ * commands never have busy waiting, so rather than complicate
+ * the error path, just remove busy waiting and continue.
+ */
+ cmd->flags &= ~MMC_RSP_BUSY;
+ }
+
+ if (!(cmd->flags & MMC_RSP_PRESENT))
+ flags = SDHCI_CMD_RESP_NONE;
+ else if (cmd->flags & MMC_RSP_136)
+ flags = SDHCI_CMD_RESP_LONG;
+ else if (cmd->flags & MMC_RSP_BUSY)
+ flags = SDHCI_CMD_RESP_SHORT_BUSY;
+ else
+ flags = SDHCI_CMD_RESP_SHORT;
+
+ if (cmd->flags & MMC_RSP_CRC)
+ flags |= SDHCI_CMD_CRC;
+ if (cmd->flags & MMC_RSP_OPCODE)
+ flags |= SDHCI_CMD_INDEX;
+
+ timeout = jiffies;
+ if (host->data_timeout)
+ timeout += nsecs_to_jiffies(host->data_timeout);
+ else if (!cmd->data && cmd->busy_timeout > 9000)
+ timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
+ else
+ timeout += 10 * HZ;
+ sdhci_mod_timer(host, cmd->mrq, timeout);
+
+ __sdhci_uhs2_send_command(host, cmd);
+
+ return true;
+}
+
+static bool sdhci_uhs2_send_command_retry(struct sdhci_host *host,
+ struct mmc_command *cmd,
+ unsigned long flags)
+ __releases(host->lock)
+ __acquires(host->lock)
+{
+ struct mmc_command *deferred_cmd = host->deferred_cmd;
+ int timeout = 10; /* Approx. 10 ms */
+ bool present;
+
+ while (!sdhci_uhs2_send_command(host, cmd)) {
+ if (!timeout--) {
+ pr_err("%s: Controller never released inhibit bit(s).\n",
+ mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ cmd->error = -EIO;
+ return false;
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ usleep_range(1000, 1250);
+
+ present = host->mmc->ops->get_cd(host->mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ /* A deferred command might disappear, handle that */
+ if (cmd == deferred_cmd && cmd != host->deferred_cmd)
+ return true;
+
+ if (sdhci_present_error(host, cmd, present))
+ return false;
+ }
+
+ if (cmd == host->deferred_cmd)
+ host->deferred_cmd = NULL;
+
+ return true;
+}
+
+static void __sdhci_uhs2_finish_command(struct sdhci_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+ u8 resp;
+ u8 ecode;
+ bool bReadA0 = 0;
+ int i;
+
+ if (host->mmc->card) {
+ if (host->mmc->card->uhs2_state & MMC_UHS2_INITIALIZED) {
+ resp = sdhci_readb(host, SDHCI_UHS2_RESPONSE + 2);
+ if (resp & UHS2_RES_NACK_MASK) {
+ ecode = (resp >> UHS2_RES_ECODE_POS) & UHS2_RES_ECODE_MASK;
+ pr_err("%s: NACK is got, ECODE=0x%x.\n",
+ mmc_hostname(host->mmc), ecode);
+ }
+ bReadA0 = 1;
+ }
+ }
+
+ if (cmd->uhs2_resp &&
+ cmd->uhs2_resp_len && cmd->uhs2_resp_len <= 20) {
+ /* Get whole response of some native CCMD, like
+ * DEVICE_INIT, ENUMERATE.
+ */
+ for (i = 0; i < cmd->uhs2_resp_len; i++)
+ cmd->uhs2_resp[i] = sdhci_readb(host, SDHCI_UHS2_RESPONSE + i);
+ } else {
+ /* Get SD CMD response and Payload for some read
+ * CCMD, like INQUIRY_CFG.
+ */
+ /* Per spec (p136), payload field is divided into
+ * a unit of DWORD and transmission order within
+ * a DWORD is big endian.
+ */
+ if (!bReadA0)
+ sdhci_readl(host, SDHCI_UHS2_RESPONSE);
+ for (i = 4; i < 20; i += 4) {
+ cmd->resp[i / 4 - 1] =
+ (sdhci_readb(host,
+ SDHCI_UHS2_RESPONSE + i) << 24) |
+ (sdhci_readb(host,
+ SDHCI_UHS2_RESPONSE + i + 1)
+ << 16) |
+ (sdhci_readb(host,
+ SDHCI_UHS2_RESPONSE + i + 2)
+ << 8) |
+ sdhci_readb(host, SDHCI_UHS2_RESPONSE + i + 3);
+ }
+ }
+}
+
+static void sdhci_uhs2_finish_command(struct sdhci_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+
+ __sdhci_uhs2_finish_command(host);
+
+ host->cmd = NULL;
+
+ if (cmd->mrq->cap_cmd_during_tfr && cmd == cmd->mrq->cmd)
+ mmc_command_done(host->mmc, cmd->mrq);
+
+ /*
+ * The host can send and interrupt when the busy state has
+ * ended, allowing us to wait without wasting CPU cycles.
+ * The busy signal uses DAT0 so this is similar to waiting
+ * for data to complete.
+ *
+ * Note: The 1.0 specification is a bit ambiguous about this
+ * feature so there might be some problems with older
+ * controllers.
+ */
+ if (cmd->flags & MMC_RSP_BUSY) {
+ if (cmd->data) {
+ DBG("Cannot wait for busy signal when also doing a data transfer");
+ } else if (!(host->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) &&
+ cmd == host->data_cmd) {
+ /* Command complete before busy is ended */
+ return;
+ }
+ }
+
+ /* Processed actual command. */
+ if (host->data && host->data_early)
+ sdhci_uhs2_finish_data(host);
+
+ if (!cmd->data)
+ __sdhci_finish_mrq(host, cmd->mrq);
+}
+
+void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct mmc_command *cmd;
+ unsigned long flags;
+ bool present;
+
+ if (!(sdhci_uhs2_mode(host))) {
+ sdhci_request(mmc, mrq);
+ return;
+ }
+
+ mrq->stop = NULL;
+ mrq->sbc = NULL;
+ if (mrq->data)
+ mrq->data->stop = NULL;
+
+ /* Firstly check card presence */
+ present = mmc->ops->get_cd(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (sdhci_present_error(host, mrq->cmd, present))
+ goto out_finish;
+
+ cmd = mrq->cmd;
+
+ if (!sdhci_uhs2_send_command(host, cmd))
+ goto out_finish;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ return;
+
+out_finish:
+ sdhci_finish_mrq(host, mrq);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_request);
+
+int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct mmc_command *cmd;
+ unsigned long flags;
+ int ret = 0;
+
+ if (!host->mmc->flags & MMC_UHS2_SUPPORT)
+ return sdhci_request_atomic(mmc, mrq);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (sdhci_present_error(host, mrq->cmd, true)) {
+ sdhci_finish_mrq(host, mrq);
+ goto out_finish;
+ }
+
+ cmd = mrq->cmd;
+
+ /*
+ * The HSQ may send a command in interrupt context without polling
+ * the busy signaling, which means we should return BUSY if controller
+ * has not released inhibit bits to allow HSQ trying to send request
+ * again in non-atomic context. So we should not finish this request
+ * here.
+ */
+ if (!sdhci_uhs2_send_command(host, cmd))
+ ret = -EBUSY;
+
+out_finish:
+ spin_unlock_irqrestore(&host->lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_request_atomic);
+
/*****************************************************************************\
* *
* Driver init/exit *
@@ -581,6 +998,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
sdhci_uhs2_start_signal_voltage_switch;
host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
host->mmc_host_ops.uhs2_control = sdhci_uhs2_control;
+ host->mmc_host_ops.request = sdhci_uhs2_request;
return 0;
}
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 3656e66fcca7..c547fc3b8b47 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -47,8 +47,6 @@
static unsigned int debug_quirks = 0;
static unsigned int debug_quirks2;
-static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
-
void sdhci_dumpregs(struct sdhci_host *host)
{
SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n");
@@ -147,10 +145,11 @@ void sdhci_enable_v4_mode(struct sdhci_host *host)
}
EXPORT_SYMBOL_GPL(sdhci_enable_v4_mode);
-static inline bool sdhci_data_line_cmd(struct mmc_command *cmd)
+bool sdhci_data_line_cmd(struct mmc_command *cmd)
{
return cmd->data || cmd->flags & MMC_RSP_BUSY;
}
+EXPORT_SYMBOL_GPL(sdhci_data_line_cmd);
static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
{
@@ -424,7 +423,7 @@ static void __sdhci_led_activate(struct sdhci_host *host)
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
}
-static void __sdhci_led_deactivate(struct sdhci_host *host)
+void __sdhci_led_deactivate(struct sdhci_host *host)
{
u8 ctrl;
@@ -435,6 +434,7 @@ static void __sdhci_led_deactivate(struct sdhci_host *host)
ctrl &= ~SDHCI_CTRL_LED;
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
}
+EXPORT_SYMBOL_GPL(__sdhci_led_deactivate);
#if IS_REACHABLE(CONFIG_LEDS_CLASS)
static void sdhci_led_control(struct led_classdev *led,
@@ -513,14 +513,15 @@ static inline void sdhci_led_deactivate(struct sdhci_host *host)
#endif
-static void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq,
- unsigned long timeout)
+void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq,
+ unsigned long timeout)
{
if (sdhci_data_line_cmd(mrq->cmd))
mod_timer(&host->data_timer, timeout);
else
mod_timer(&host->timer, timeout);
}
+EXPORT_SYMBOL_GPL(sdhci_mod_timer);
static void sdhci_del_timer(struct sdhci_host *host, struct mmc_request *mrq)
{
@@ -1087,8 +1088,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
__sdhci_set_timeout(host, cmd);
}
-static void sdhci_initialize_data(struct sdhci_host *host,
- struct mmc_data *data)
+void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data)
{
WARN_ON(host->data);
@@ -1101,6 +1101,7 @@ static void sdhci_initialize_data(struct sdhci_host *host,
host->data_early = 0;
host->data->bytes_xfered = 0;
}
+EXPORT_SYMBOL_GPL(sdhci_initialize_data);
static inline void sdhci_set_block_info(struct sdhci_host *host,
struct mmc_data *data)
@@ -1123,12 +1124,8 @@ static inline void sdhci_set_block_info(struct sdhci_host *host,
}
}
-static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
+void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data)
{
- struct mmc_data *data = cmd->data;
-
- sdhci_initialize_data(host, data);
-
if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
struct scatterlist *sg;
unsigned int length_mask, offset_mask;
@@ -1213,6 +1210,16 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
}
sdhci_set_transfer_irqs(host);
+}
+EXPORT_SYMBOL_GPL(sdhci_prepare_dma);
+
+static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
+{
+ struct mmc_data *data = cmd->data;
+
+ sdhci_initialize_data(host, data);
+
+ sdhci_prepare_dma(host, data);
sdhci_set_block_info(host, data);
}
@@ -1255,8 +1262,7 @@ static struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
return data->flags & MMC_DATA_WRITE ? host->tx_chan : host->rx_chan;
}
-static int sdhci_external_dma_setup(struct sdhci_host *host,
- struct mmc_command *cmd)
+int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd)
{
int ret, i;
enum dma_transfer_direction dir;
@@ -1309,8 +1315,9 @@ static int sdhci_external_dma_setup(struct sdhci_host *host,
return ret;
}
+EXPORT_SYMBOL_GPL(sdhci_external_dma_setup);
-static void sdhci_external_dma_release(struct sdhci_host *host)
+void sdhci_external_dma_release(struct sdhci_host *host)
{
if (host->tx_chan) {
dma_release_channel(host->tx_chan);
@@ -1324,9 +1331,10 @@ static void sdhci_external_dma_release(struct sdhci_host *host)
sdhci_switch_external_dma(host, false);
}
+EXPORT_SYMBOL_GPL(sdhci_external_dma_release);
-static void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
- struct mmc_command *cmd)
+void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
+ struct mmc_command *cmd)
{
struct mmc_data *data = cmd->data;
@@ -1337,6 +1345,7 @@ static void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
sdhci_set_block_info(host, data);
}
+EXPORT_SYMBOL(__sdhci_external_dma_prepare_data);
static void sdhci_external_dma_prepare_data(struct sdhci_host *host,
struct mmc_command *cmd)
@@ -1351,8 +1360,8 @@ static void sdhci_external_dma_prepare_data(struct sdhci_host *host,
}
}
-static void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
- struct mmc_command *cmd)
+void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
+ struct mmc_command *cmd)
{
struct dma_chan *chan;
@@ -1363,6 +1372,7 @@ static void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
if (chan)
dma_async_issue_pending(chan);
}
+EXPORT_SYMBOL_GPL(sdhci_external_dma_pre_transfer);
#else
@@ -1414,8 +1424,7 @@ static inline bool sdhci_auto_cmd23(struct sdhci_host *host,
return mrq->sbc && (host->flags & SDHCI_AUTO_CMD23);
}
-static inline bool sdhci_manual_cmd23(struct sdhci_host *host,
- struct mmc_request *mrq)
+static inline bool sdhci_manual_cmd23(struct sdhci_host *host, struct mmc_request *mrq)
{
return mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23);
}
@@ -1530,7 +1539,7 @@ static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
WARN_ON(i >= SDHCI_MAX_MRQS);
}
-static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
+void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
{
if (host->cmd && host->cmd->mrq == mrq)
host->cmd = NULL;
@@ -1554,15 +1563,17 @@ static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
if (!sdhci_has_requests(host))
sdhci_led_deactivate(host);
}
+EXPORT_SYMBOL_GPL(__sdhci_finish_mrq);
-static void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
+void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
{
__sdhci_finish_mrq(host, mrq);
queue_work(host->complete_wq, &host->complete_work);
}
+EXPORT_SYMBOL_GPL(sdhci_finish_mrq);
-static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
+void __sdhci_finish_data_common(struct sdhci_host *host)
{
struct mmc_command *data_cmd = host->data_cmd;
struct mmc_data *data = host->data;
@@ -1596,6 +1607,14 @@ static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
data->bytes_xfered = 0;
else
data->bytes_xfered = data->blksz * data->blocks;
+}
+EXPORT_SYMBOL_GPL(__sdhci_finish_data_common);
+
+static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
+{
+ struct mmc_data *data = host->data;
+
+ __sdhci_finish_data_common(host);
/*
* Need to send CMD12 if -
@@ -1681,8 +1700,6 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
sdhci_prepare_data(host, cmd);
}
- sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
-
sdhci_set_transfer_mode(host, cmd);
if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
@@ -1725,13 +1742,15 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
if (host->use_external_dma)
sdhci_external_dma_pre_transfer(host, cmd);
+ sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
+
sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
return true;
}
-static bool sdhci_present_error(struct sdhci_host *host,
- struct mmc_command *cmd, bool present)
+bool sdhci_present_error(struct sdhci_host *host,
+ struct mmc_command *cmd, bool present)
{
if (!present || host->flags & SDHCI_DEVICE_DEAD) {
cmd->error = -ENOMEDIUM;
@@ -1740,6 +1759,7 @@ static bool sdhci_present_error(struct sdhci_host *host,
return false;
}
+EXPORT_SYMBOL_GPL(sdhci_present_error);
static bool sdhci_send_command_retry(struct sdhci_host *host,
struct mmc_command *cmd,
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index c2f989dc2361..3b87fcae1294 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -831,6 +831,20 @@ static inline void sdhci_read_caps(struct sdhci_host *host)
__sdhci_read_caps(host, NULL, NULL, NULL);
}
+bool sdhci_data_line_cmd(struct mmc_command *cmd);
+void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, unsigned long timeout);
+void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data);
+void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data);
+#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA)
+int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd);
+void sdhci_external_dma_release(struct sdhci_host *host);
+void __sdhci_external_dma_prepare_data(struct sdhci_host *host, struct mmc_command *cmd);
+void sdhci_external_dma_pre_transfer(struct sdhci_host *host, struct mmc_command *cmd);
+#endif
+void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
+void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
+void __sdhci_finish_data_common(struct sdhci_host *host);
+bool sdhci_present_error(struct sdhci_host *host, struct mmc_command *cmd, bool present);
u16 sdhci_calc_clk(struct sdhci_host *host, unsigned int clock,
unsigned int *actual_clock);
void sdhci_set_clock(struct sdhci_host *host, unsigned int clock);
--
2.25.1
Embed UHS-II access/control functionality into the MMC request
processing flow.
Signed-off-by: Ulf Hansson <[email protected]>
Signed-off-by: Jason Lai <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/core/block.c | 6 +-
drivers/mmc/core/core.c | 20 +
drivers/mmc/core/mmc_ops.c | 25 +-
drivers/mmc/core/mmc_ops.h | 1 +
drivers/mmc/core/sd.c | 11 +-
drivers/mmc/core/sd.h | 3 +
drivers/mmc/core/sd_ops.c | 13 +
drivers/mmc/core/sd_ops.h | 3 +
drivers/mmc/core/sd_uhs2.c | 1171 +++++++++++++++++++++++++++++++++++-
9 files changed, 1206 insertions(+), 47 deletions(-)
diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
index 20da7ed43e6d..d3e8ec43cdd5 100644
--- a/drivers/mmc/core/block.c
+++ b/drivers/mmc/core/block.c
@@ -1596,6 +1596,9 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
struct request *req = mmc_queue_req_to_req(mqrq);
struct mmc_blk_data *md = mq->blkdata;
bool do_rel_wr, do_data_tag;
+ bool do_multi;
+
+ do_multi = (card->uhs2_state & MMC_UHS2_INITIALIZED) ? true : false;
mmc_blk_data_prep(mq, mqrq, recovery_mode, &do_rel_wr, &do_data_tag);
@@ -1606,7 +1609,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
brq->cmd.arg <<= 9;
brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
- if (brq->data.blocks > 1 || do_rel_wr) {
+ if (brq->data.blocks > 1 || do_rel_wr || do_multi) {
/* SPI multiblock writes terminate using a special
* token, not a STOP_TRANSMISSION request.
*/
@@ -1619,6 +1622,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
brq->mrq.stop = NULL;
readcmd = MMC_READ_SINGLE_BLOCK;
writecmd = MMC_WRITE_BLOCK;
+ brq->cmd.uhs2_tmode0_flag = 1;
}
brq->cmd.opcode = rq_data_dir(req) == READ ? readcmd : writecmd;
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index ad1a40446813..01f8216885ad 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -334,6 +334,8 @@ static int mmc_mrq_prep(struct mmc_host *host, struct mmc_request *mrq)
int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
+ struct uhs2_command uhs2_cmd;
+ __be32 payload[4]; /* for maximum size */
int err;
init_completion(&mrq->cmd_completion);
@@ -351,6 +353,14 @@ int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
if (err)
return err;
+ if (host->card) {
+ if (host->card->uhs2_state & MMC_UHS2_INITIALIZED) {
+ uhs2_cmd.payload = payload;
+ mrq->cmd->uhs2_cmd = &uhs2_cmd;
+ mmc_uhs2_prepare_cmd(host, mrq);
+ }
+ }
+
led_trigger_event(host->led, LED_FULL);
__mmc_start_request(host, mrq);
@@ -430,6 +440,8 @@ EXPORT_SYMBOL(mmc_wait_for_req_done);
*/
int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq)
{
+ struct uhs2_command uhs2_cmd;
+ __be32 payload[4]; /* for maximum size */
int err;
/*
@@ -450,6 +462,14 @@ int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq)
if (err)
goto out_err;
+ if (host->card) {
+ if (host->card->uhs2_state & MMC_UHS2_INITIALIZED) {
+ uhs2_cmd.payload = payload;
+ mrq->cmd->uhs2_cmd = &uhs2_cmd;
+ mmc_uhs2_prepare_cmd(host, mrq);
+ }
+ }
+
err = host->cqe_ops->cqe_request(host, mrq);
if (err)
goto out_err;
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index 81c55bfd6e0c..daa1f4ccd99a 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -144,10 +144,24 @@ int mmc_set_dsr(struct mmc_host *host)
return mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
}
+int __mmc_go_idle(struct mmc_host *host)
+{
+ struct mmc_command cmd = {};
+ int err;
+
+ cmd.opcode = MMC_GO_IDLE_STATE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ mmc_delay(1);
+
+ return err;
+}
+
int mmc_go_idle(struct mmc_host *host)
{
int err;
- struct mmc_command cmd = {};
/*
* Non-SPI hosts need to prevent chipselect going active during
@@ -163,13 +177,7 @@ int mmc_go_idle(struct mmc_host *host)
mmc_delay(1);
}
- cmd.opcode = MMC_GO_IDLE_STATE;
- cmd.arg = 0;
- cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
-
- err = mmc_wait_for_cmd(host, &cmd, 0);
-
- mmc_delay(1);
+ err = __mmc_go_idle(host);
if (!mmc_host_is_spi(host)) {
mmc_set_chip_select(host, MMC_CS_DONTCARE);
@@ -300,6 +308,7 @@ int mmc_send_adtc_data(struct mmc_card *card, struct mmc_host *host, u32 opcode,
* not R1 plus a data block.
*/
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+ cmd.uhs2_tmode0_flag = 1;
data.blksz = len;
data.blocks = 1;
diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h
index 09ffbc00908b..abda7492d578 100644
--- a/drivers/mmc/core/mmc_ops.h
+++ b/drivers/mmc/core/mmc_ops.h
@@ -25,6 +25,7 @@ struct mmc_command;
int mmc_select_card(struct mmc_card *card);
int mmc_deselect_cards(struct mmc_host *host);
int mmc_set_dsr(struct mmc_host *host);
+int __mmc_go_idle(struct mmc_host *host);
int mmc_go_idle(struct mmc_host *host);
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
int mmc_set_relative_addr(struct mmc_card *card);
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 72b664ed90cf..4de4914c5ced 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -207,7 +207,7 @@ static int mmc_decode_csd(struct mmc_card *card)
/*
* Given a 64-bit response, decode to our card SCR structure.
*/
-static int mmc_decode_scr(struct mmc_card *card)
+int mmc_decode_scr(struct mmc_card *card)
{
struct sd_scr *scr = &card->scr;
unsigned int scr_struct;
@@ -1616,11 +1616,6 @@ static void mmc_sd_detect(struct mmc_host *host)
}
}
-static int sd_can_poweroff_notify(struct mmc_card *card)
-{
- return card->ext_power.feature_support & SD_EXT_POWER_OFF_NOTIFY;
-}
-
static int sd_busy_poweroff_notify_cb(void *cb_data, bool *busy)
{
struct sd_busy_data *data = cb_data;
@@ -1644,7 +1639,7 @@ static int sd_busy_poweroff_notify_cb(void *cb_data, bool *busy)
return 0;
}
-static int sd_poweroff_notify(struct mmc_card *card)
+int sd_poweroff_notify(struct mmc_card *card)
{
struct sd_busy_data cb_data;
u8 *reg_buf;
@@ -1692,7 +1687,7 @@ static int _mmc_sd_suspend(struct mmc_host *host)
if (mmc_card_suspended(card))
goto out;
- if (sd_can_poweroff_notify(card))
+ if (mmc_card_can_poweroff_notify(card))
err = sd_poweroff_notify(card);
else if (!mmc_host_is_spi(host))
err = mmc_deselect_cards(host);
diff --git a/drivers/mmc/core/sd.h b/drivers/mmc/core/sd.h
index 1af5a038bae9..b573a809a0f4 100644
--- a/drivers/mmc/core/sd.h
+++ b/drivers/mmc/core/sd.h
@@ -17,4 +17,7 @@ int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,
unsigned mmc_sd_get_max_clock(struct mmc_card *card);
int mmc_sd_switch_hs(struct mmc_card *card);
+/* These call back functions were also used by UHS2 sd card */
+int sd_poweroff_notify(struct mmc_card *card);
+
#endif
diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c
index ef8d1dce5af1..44a47a373b04 100644
--- a/drivers/mmc/core/sd_ops.c
+++ b/drivers/mmc/core/sd_ops.c
@@ -27,6 +27,17 @@ int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
if (WARN_ON(card && card->host != host))
return -EINVAL;
+ /*
+ * UHS2 packet has APP bit so only set APP_CMD flag here.
+ * Will set the APP bit when assembling UHS2 packet.
+ */
+ if (host->card) {
+ if (host->card->uhs2_state & MMC_UHS2_INITIALIZED) {
+ host->uhs2_ios.is_APP_CMD = true;
+ return 0;
+ }
+ }
+
cmd.opcode = MMC_APP_CMD;
if (card) {
@@ -281,6 +292,7 @@ int mmc_app_send_scr(struct mmc_card *card)
cmd.opcode = SD_APP_SEND_SCR;
cmd.arg = 0;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+ cmd.uhs2_tmode0_flag = 1;
data.blksz = 8;
data.blocks = 1;
@@ -344,6 +356,7 @@ int mmc_app_sd_status(struct mmc_card *card, void *ssr)
cmd.opcode = SD_APP_SD_STATUS;
cmd.arg = 0;
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_ADTC;
+ cmd.uhs2_tmode0_flag = 1;
data.blksz = 64;
data.blocks = 1;
diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h
index 3ba7b3cf4652..29c802dec988 100644
--- a/drivers/mmc/core/sd_ops.h
+++ b/drivers/mmc/core/sd_ops.h
@@ -11,6 +11,7 @@
#include <linux/types.h>
struct mmc_card;
+struct mmc_command;
struct mmc_host;
int mmc_app_set_bus_width(struct mmc_card *card, int width);
@@ -19,10 +20,12 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr);
int mmc_send_if_cond_pcie(struct mmc_host *host, u32 ocr);
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
int mmc_app_send_scr(struct mmc_card *card);
+int mmc_decode_scr(struct mmc_card *card);
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
u8 value, u8 *resp);
int mmc_app_sd_status(struct mmc_card *card, void *ssr);
int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card);
+void mmc_uhs2_prepare_cmd(struct mmc_host *host, struct mmc_request *mrq);
#endif
diff --git a/drivers/mmc/core/sd_uhs2.c b/drivers/mmc/core/sd_uhs2.c
index 800957f74632..a79eb08ec540 100644
--- a/drivers/mmc/core/sd_uhs2.c
+++ b/drivers/mmc/core/sd_uhs2.c
@@ -1,48 +1,125 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 Linaro Ltd
- *
* Author: Ulf Hansson <[email protected]>
*
+ * Copyright (C) 2014 Intel Corp, All Rights Reserved.
+ * Author: Yi Sun <[email protected]>
+ *
+ * Copyright (C) 2020 Genesys Logic, Inc.
+ * Authors: Ben Chuang <[email protected]>
+ *
+ * Copyright (C) 2020 Linaro Limited
+ * Author: AKASHI Takahiro <[email protected]>
+ *
+ * Copyright (C) 2022 Genesys Logic, Inc.
+ * Authors: Jason Lai <[email protected]>
+ *
* Support for SD UHS-II cards
*/
#include <linux/err.h>
+#include <linux/pm_runtime.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/sd_uhs2.h>
+#include "card.h"
#include "core.h"
#include "bus.h"
#include "sd.h"
+#include "sd_ops.h"
#include "mmc_ops.h"
+#define UHS2_WAIT_CFG_COMPLETE_PERIOD_US (1 * 1000) /* 1ms */
+#define UHS2_WAIT_CFG_COMPLETE_TIMEOUT_MS 100 /* 100ms */
+
static const unsigned int sd_uhs2_freqs[] = { 52000000, 26000000 };
+int sd_uhs2_reinit(struct mmc_host *host);
-static int sd_uhs2_set_ios(struct mmc_host *host)
+/*
+ * Internal function that does the actual ios call to the host driver,
+ * optionally printing some debug output.
+ */
+static inline int sd_uhs2_set_ios(struct mmc_host *host)
{
struct mmc_ios *ios = &host->ios;
+ pr_debug("%s: clock %uHz powermode %u Vdd %u timing %u\n",
+ mmc_hostname(host), ios->clock, ios->power_mode, ios->vdd, ios->timing);
+
return host->ops->uhs2_set_ios(host, ios);
}
static int sd_uhs2_power_up(struct mmc_host *host)
{
+ int err;
+
+ if (host->ios.power_mode == MMC_POWER_ON)
+ return 0;
+
host->ios.vdd = fls(host->ocr_avail) - 1;
host->ios.clock = host->f_init;
host->ios.timing = MMC_TIMING_SD_UHS2;
- host->ios.power_mode = MMC_POWER_UP;
+ host->ios.power_mode = MMC_POWER_ON;
- return sd_uhs2_set_ios(host);
+ err = sd_uhs2_set_ios(host);
+
+ mmc_delay(host->uhs2_ios.power_delay_ms);
+
+ return err;
}
-static void sd_uhs2_power_off(struct mmc_host *host)
+static int sd_uhs2_power_off(struct mmc_host *host)
{
+ if (host->ios.power_mode == MMC_POWER_OFF)
+ return 0;
+
host->ios.vdd = 0;
host->ios.clock = 0;
host->ios.timing = MMC_TIMING_LEGACY;
host->ios.power_mode = MMC_POWER_OFF;
- sd_uhs2_set_ios(host);
+ return sd_uhs2_set_ios(host);
+}
+
+/**
+ * sd_uhs2_cmd_assemble() - build up UHS-II command packet which is embedded in
+ * mmc_command structure
+ * @cmd: MMC command to executed
+ * @uhs2_cmd: UHS2 command corresponded to MMC command
+ * @header: Header field of UHS-II command cxpacket
+ * @arg: Argument field of UHS-II command packet
+ * @payload: Payload field of UHS-II command packet
+ * @plen: Payload length
+ * @resp: Response buffer is allocated by caller and it is used to keep
+ * the response of CM-TRAN command. For SD-TRAN command, uhs2_resp
+ * should be null and SD-TRAN command response should be stored in
+ * resp of mmc_command.
+ * @resp_len: Response buffer length
+ *
+ * The uhs2_command structure contains message packets which are transmited/
+ * received on UHS-II bus. This function fills in the contents of uhs2_command
+ * structure and embededs UHS2 command into mmc_command structure, which is used
+ * in legacy SD operation functions.
+ *
+ */
+static void sd_uhs2_cmd_assemble(struct mmc_command *cmd,
+ struct uhs2_command *uhs2_cmd,
+ u16 header, u16 arg, __be32 *payload,
+ u8 plen, u8 *resp, u8 resp_len)
+{
+ uhs2_cmd->header = header;
+ uhs2_cmd->arg = arg;
+ uhs2_cmd->payload = payload;
+ uhs2_cmd->payload_len = plen * sizeof(u32);
+ uhs2_cmd->packet_len = uhs2_cmd->payload_len + 4;
+
+ cmd->uhs2_cmd = uhs2_cmd;
+ cmd->uhs2_resp = resp;
+ cmd->uhs2_resp_len = resp_len;
}
/*
@@ -52,7 +129,15 @@ static void sd_uhs2_power_off(struct mmc_host *host)
*/
static int sd_uhs2_phy_init(struct mmc_host *host)
{
- return 0;
+ int err = 0;
+
+ err = host->ops->uhs2_control(host, UHS2_PHY_INIT);
+ if (err) {
+ pr_err("%s: failed to initial phy for UHS-II!\n",
+ mmc_hostname(host));
+ }
+
+ return err;
}
/*
@@ -61,6 +146,88 @@ static int sd_uhs2_phy_init(struct mmc_host *host)
*/
static int sd_uhs2_dev_init(struct mmc_host *host)
{
+ struct mmc_command cmd = {0};
+ struct uhs2_command uhs2_cmd = {};
+ u32 cnt;
+ u32 dap, gap, resp_gap;
+ u16 header, arg;
+ __be32 payload[UHS2_DEV_INIT_PAYLOAD_LEN];
+ u8 gd = 0;
+ u8 resp[UHS2_DEV_ENUM_RESP_LEN] = {0};
+ int err;
+
+ dap = host->uhs2_caps.dap;
+ gap = host->uhs2_caps.gap;
+
+ /*
+ * Refer to UHS-II Addendum Version 1.02 Figure 6-21 to see DEVICE_INIT CCMD format.
+ * Head:
+ * - Control Write(R/W=1) with 4-Byte payload(PLEN=01b).
+ * - IOADR = CMD_BASE + 002h
+ * Payload:
+ * - bit [3:0] : GAP(Group Allocated Power)
+ * - bit [7:4] : GD(Group Descriptor)
+ * - bit [11] : Complete Flag
+ * - bit [15:12]: DAP(Device Allocated Power)
+ */
+ header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD;
+ arg = ((UHS2_DEV_CMD_DEVICE_INIT & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_WRITE |
+ UHS2_NATIVE_CMD_PLEN_4B |
+ (UHS2_DEV_CMD_DEVICE_INIT >> 8);
+
+ /*
+ * Refer to UHS-II Addendum Version 1.02 section 6.3.1.
+ * Max. time from DEVICE_INIT CCMD EOP reception on Device
+ * Rx to its SOP transmission on Device Tx(Tfwd_init_cmd) is
+ * 1 second.
+ */
+ cmd.busy_timeout = 1000;
+
+ /*
+ * Refer to UHS-II Addendum Version 1.02 section 6.2.6.3.
+ * When the number of the DEVICE_INIT commands is reach to
+ * 30 tiems, Host shall stop issuing DEVICE_INIT command
+ * and regard it as an error.
+ */
+ for (cnt = 0; cnt < 30; cnt++) {
+ payload[0] = ((dap & 0xF) << 12) |
+ UHS2_DEV_INIT_COMPLETE_FLAG |
+ ((gd & 0xF) << 4) |
+ (gap & 0xF);
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg,
+ payload, UHS2_DEV_INIT_PAYLOAD_LEN,
+ resp, UHS2_DEV_INIT_RESP_LEN);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ if (resp[3] != (UHS2_DEV_CMD_DEVICE_INIT & 0xFF)) {
+ pr_err("%s: DEVICE_INIT response is wrong!\n",
+ mmc_hostname(host));
+ return -EIO;
+ }
+
+ if (resp[5] & 0x8) {
+ host->uhs2_caps.group_desc = gd;
+ return 0;
+ }
+ resp_gap = resp[4] & 0x0F;
+ if (gap == resp_gap)
+ gd++;
+ }
+ if (cnt == 30) {
+ pr_err("%s: DEVICE_INIT fail, already 30 times!\n",
+ mmc_hostname(host));
+ return -EIO;
+ }
+
return 0;
}
@@ -71,6 +238,52 @@ static int sd_uhs2_dev_init(struct mmc_host *host)
*/
static int sd_uhs2_enum(struct mmc_host *host, u32 *node_id)
{
+ struct mmc_command cmd = {0};
+ struct uhs2_command uhs2_cmd = {};
+ u16 header, arg;
+ __be32 payload[UHS2_DEV_ENUM_PAYLOAD_LEN];
+ u8 id_f = 0xF, id_l = 0x0;
+ u8 resp[UHS2_DEV_ENUM_RESP_LEN] = {0};
+ int err;
+
+ /*
+ * Refer to UHS-II Addendum Version 1.02 Figure 6-28 to see ENUMERATE CCMD format.
+ * Header:
+ * - Control Write(R/W=1) with 4-Byte payload(PLEN=01b).
+ * - IOADR = CMD_BASE + 003h
+ * Payload:
+ * - bit [3:0]: ID_L(Last Node ID)
+ * - bit [7:4]: ID_F(First Node ID)
+ */
+ header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD;
+ arg = ((UHS2_DEV_CMD_ENUMERATE & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_WRITE |
+ UHS2_NATIVE_CMD_PLEN_4B |
+ (UHS2_DEV_CMD_ENUMERATE >> 8);
+
+ payload[0] = (id_f << 4) | id_l;
+ payload[0] = cpu_to_be32(payload[0]);
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_DEV_ENUM_PAYLOAD_LEN,
+ resp, UHS2_DEV_ENUM_RESP_LEN);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ if (resp[3] != (UHS2_DEV_CMD_ENUMERATE & 0xFF)) {
+ pr_err("%s: ENUMERATE response is wrong!\n",
+ mmc_hostname(host));
+ return -EIO;
+ }
+
+ id_f = (resp[4] >> 4) & 0xF;
+ id_l = resp[4] & 0xF;
+ *node_id = id_f;
+
return 0;
}
@@ -81,6 +294,181 @@ static int sd_uhs2_enum(struct mmc_host *host, u32 *node_id)
*/
static int sd_uhs2_config_read(struct mmc_host *host, struct mmc_card *card)
{
+ struct mmc_command cmd = {0};
+ struct uhs2_command uhs2_cmd = {};
+ u16 header, arg;
+ u32 cap;
+ int err;
+
+ /*
+ * Use Control Read CCMD to read Generic Capability from Configuration Register.
+ * - Control Write(R/W=1) with 4-Byte payload(PLEN=01b).
+ * - IOADR = Generic Capability Register(CFG_BASE + 000h)
+ */
+ header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | card->uhs2_config.node_id;
+ arg = ((UHS2_DEV_CONFIG_GEN_CAPS & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_READ |
+ UHS2_NATIVE_CMD_PLEN_4B |
+ (UHS2_DEV_CONFIG_GEN_CAPS >> 8);
+
+ /*
+ * There is no payload because per spec, there should be
+ * no payload field for read CCMD.
+ * Plen is set in arg. Per spec, plen for read CCMD
+ * represents the len of read data which is assigned in payload
+ * of following RES (p136).
+ */
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, NULL, 0, NULL, 0);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /*
+ * Generic Capability Register:
+ * bit [7:0] : Reserved
+ * bit [13:8] : Device-Specific Number of Lanes and Functionality
+ * bit 8: 2L-HD
+ * bit 9: 2D-1U FD
+ * bit 10: 1D-2U FD
+ * bit 11: 2D-2U FD
+ * Others: Reserved
+ * bit [14] : DADR Length
+ * 0: 4 bytes
+ * 1: Reserved
+ * bit [23:16]: Application Type
+ * bit 16: 0=Non-SD memory, 1=SD memory
+ * bit 17: 0=Non-SDIO, 1=SDIO
+ * bit 18: 0=Card, 1=Embedded
+ * bit [63:24]: Reserved
+ */
+ cap = cmd.resp[0];
+ card->uhs2_config.n_lanes =
+ (cap >> UHS2_DEV_CONFIG_N_LANES_POS) &
+ UHS2_DEV_CONFIG_N_LANES_MASK;
+ card->uhs2_config.dadr_len =
+ (cap >> UHS2_DEV_CONFIG_DADR_POS) &
+ UHS2_DEV_CONFIG_DADR_MASK;
+ card->uhs2_config.app_type =
+ (cap >> UHS2_DEV_CONFIG_APP_POS) &
+ UHS2_DEV_CONFIG_APP_MASK;
+
+ /*
+ * Use Control Read CCMD to read PHY Capability from Configuration Register.
+ * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
+ * - IOADR = PHY Capability Register(CFG_BASE + 002h)
+ */
+ arg = ((UHS2_DEV_CONFIG_PHY_CAPS & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_READ |
+ UHS2_NATIVE_CMD_PLEN_8B |
+ (UHS2_DEV_CONFIG_PHY_CAPS >> 8);
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, NULL, 0, NULL, 0);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /*
+ * PHY Capability Register:
+ * bit [3:0] : PHY Minor Revision
+ * bit [5:4] : PHY Major Revision
+ * bit [15] : Support Hibernate Mode
+ * 0: Not support Hibernate Mode
+ * 1: Support Hibernate Mode
+ * bit [31:16]: Reserved
+ * bit [35:32]: Device-Specific N_LSS_SYN
+ * bit [39:36]: Device-Specific N_LSS_DIR
+ * bit [63:40]: Reserved
+ */
+ cap = cmd.resp[0];
+ card->uhs2_config.phy_minor_rev =
+ cap & UHS2_DEV_CONFIG_PHY_MINOR_MASK;
+ card->uhs2_config.phy_major_rev =
+ (cap >> UHS2_DEV_CONFIG_PHY_MAJOR_POS) &
+ UHS2_DEV_CONFIG_PHY_MAJOR_MASK;
+ card->uhs2_config.can_hibernate =
+ (cap >> UHS2_DEV_CONFIG_CAN_HIBER_POS) &
+ UHS2_DEV_CONFIG_CAN_HIBER_MASK;
+
+ cap = cmd.resp[1];
+ card->uhs2_config.n_lss_sync =
+ cap & UHS2_DEV_CONFIG_N_LSS_SYN_MASK;
+ card->uhs2_config.n_lss_dir =
+ (cap >> UHS2_DEV_CONFIG_N_LSS_DIR_POS) &
+ UHS2_DEV_CONFIG_N_LSS_DIR_MASK;
+ if (card->uhs2_config.n_lss_sync == 0)
+ card->uhs2_config.n_lss_sync = 16 << 2;
+ else
+ card->uhs2_config.n_lss_sync <<= 2;
+
+ if (card->uhs2_config.n_lss_dir == 0)
+ card->uhs2_config.n_lss_dir = 16 << 3;
+ else
+ card->uhs2_config.n_lss_dir <<= 3;
+
+ /*
+ * Use Control Read CCMD to read LINK/TRAN Capability from Configuration Register.
+ * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
+ * - IOADR = LINK/TRAN Capability Register(CFG_BASE + 004h)
+ */
+ arg = ((UHS2_DEV_CONFIG_LINK_TRAN_CAPS & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_READ |
+ UHS2_NATIVE_CMD_PLEN_8B |
+ (UHS2_DEV_CONFIG_LINK_TRAN_CAPS >> 8);
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, NULL, 0, NULL, 0);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /*
+ * LINK/TRAN Capability Register:
+ * bit [3:0] : LINK_TRAN Minor Revision
+ * bit [5:4] : LINK/TRAN Major Revision
+ * bit [7:6] : Reserved
+ * bit [15:8] : Device-Specific N_FCU
+ * bit [18:16]: Device Type
+ * 001b=Host
+ * 010b=Device
+ * 011b=Reserved for CMD issuable Device
+ * bit [19] : Reserved
+ * bit [31:20]: Device-Specific MAX_BLKLEN
+ * bit [39:32]: Device-Specific N_DATA_GAP
+ * bit [63:40]: Reserved
+ */
+ cap = cmd.resp[0];
+ card->uhs2_config.link_minor_rev =
+ cap & UHS2_DEV_CONFIG_LT_MINOR_MASK;
+ card->uhs2_config.link_major_rev =
+ (cap >> UHS2_DEV_CONFIG_LT_MAJOR_POS) &
+ UHS2_DEV_CONFIG_LT_MAJOR_MASK;
+ card->uhs2_config.n_fcu =
+ (cap >> UHS2_DEV_CONFIG_N_FCU_POS) &
+ UHS2_DEV_CONFIG_N_FCU_MASK;
+ card->uhs2_config.dev_type =
+ (cap >> UHS2_DEV_CONFIG_DEV_TYPE_POS) &
+ UHS2_DEV_CONFIG_DEV_TYPE_MASK;
+ card->uhs2_config.maxblk_len =
+ (cap >> UHS2_DEV_CONFIG_MAX_BLK_LEN_POS) &
+ UHS2_DEV_CONFIG_MAX_BLK_LEN_MASK;
+
+ cap = cmd.resp[1];
+ card->uhs2_config.n_data_gap =
+ cap & UHS2_DEV_CONFIG_N_DATA_GAP_MASK;
+ if (card->uhs2_config.n_fcu == 0)
+ card->uhs2_config.n_fcu = 256;
+
return 0;
}
@@ -95,9 +483,407 @@ static int sd_uhs2_config_read(struct mmc_host *host, struct mmc_card *card)
*/
static int sd_uhs2_config_write(struct mmc_host *host, struct mmc_card *card)
{
+ struct mmc_command cmd = {0};
+ struct uhs2_command uhs2_cmd = {};
+ u16 header, arg;
+ __be32 payload[UHS2_CFG_WRITE_PAYLOAD_LEN];
+ u8 nMinDataGap;
+ int err;
+ u8 resp[5] = {0};
+
+ /*
+ * Use Control Write CCMD to set Generic Setting in Configuration Register.
+ * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
+ * - IOADR = Generic Setting Register(CFG_BASE + 008h)
+ * - Payload = New contents to be written to Generic Setting Register
+ */
+ header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | card->uhs2_config.node_id;
+ arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_WRITE |
+ UHS2_NATIVE_CMD_PLEN_8B |
+ (UHS2_DEV_CONFIG_GEN_SET >> 8);
+
+ if (card->uhs2_config.n_lanes == UHS2_DEV_CONFIG_2L_HD_FD &&
+ host->uhs2_caps.n_lanes == UHS2_DEV_CONFIG_2L_HD_FD) {
+ /* Support HD */
+ host->uhs2_ios.is_2L_HD_mode = true;
+ nMinDataGap = 1;
+ } else {
+ /* Only support 2L-FD so far */
+ host->uhs2_ios.is_2L_HD_mode = false;
+ nMinDataGap = 3;
+ }
+
+ /*
+ * Most UHS-II cards only support FD and 2L-HD mode. Other lane numbers
+ * defined in UHS-II addendem Ver1.01 are optional.
+ */
+ host->uhs2_caps.n_lanes_set = UHS2_DEV_CONFIG_GEN_SET_2L_FD_HD;
+ card->uhs2_config.n_lanes_set = UHS2_DEV_CONFIG_GEN_SET_2L_FD_HD;
+
+ payload[0] = card->uhs2_config.n_lanes_set << UHS2_DEV_CONFIG_N_LANES_POS;
+ payload[1] = 0;
+ payload[0] = cpu_to_be32(payload[0]);
+ payload[1] = cpu_to_be32(payload[1]);
+
+ /*
+ * There is no payload because per spec, there should be
+ * no payload field for read CCMD.
+ * Plen is set in arg. Per spec, plen for read CCMD
+ * represents the len of read data which is assigned in payload
+ * of following RES (p136).
+ */
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
+ NULL, 0);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /*
+ * Use Control Write CCMD to set PHY Setting in Configuration Register.
+ * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
+ * - IOADR = PHY Setting Register(CFG_BASE + 00Ah)
+ * - Payload = New contents to be written to PHY Setting Register
+ */
+ arg = ((UHS2_DEV_CONFIG_PHY_SET & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_WRITE |
+ UHS2_NATIVE_CMD_PLEN_8B |
+ (UHS2_DEV_CONFIG_PHY_SET >> 8);
+
+ if (host->uhs2_caps.speed_range == UHS2_DEV_CONFIG_PHY_SET_SPEED_B) {
+ card->uhs2_state |= MMC_UHS2_SPEED_B;
+ card->uhs2_config.speed_range_set =
+ UHS2_DEV_CONFIG_PHY_SET_SPEED_B;
+ } else {
+ card->uhs2_config.speed_range_set = UHS2_DEV_CONFIG_PHY_SET_SPEED_A;
+ card->uhs2_state &= ~MMC_UHS2_SPEED_B;
+ }
+
+ payload[0] = card->uhs2_config.speed_range_set << UHS2_DEV_CONFIG_PHY_SET_SPEED_POS;
+
+ card->uhs2_config.n_lss_sync_set = (max(card->uhs2_config.n_lss_sync,
+ host->uhs2_caps.n_lss_sync) >> 2) &
+ UHS2_DEV_CONFIG_N_LSS_SYN_MASK;
+ host->uhs2_caps.n_lss_sync_set = card->uhs2_config.n_lss_sync_set;
+
+ card->uhs2_config.n_lss_dir_set = (max(card->uhs2_config.n_lss_dir,
+ host->uhs2_caps.n_lss_dir) >> 3) &
+ UHS2_DEV_CONFIG_N_LSS_DIR_MASK;
+ host->uhs2_caps.n_lss_dir_set = card->uhs2_config.n_lss_dir_set;
+
+ payload[1] = (card->uhs2_config.n_lss_dir_set << UHS2_DEV_CONFIG_N_LSS_DIR_POS) |
+ card->uhs2_config.n_lss_sync_set;
+ payload[0] = cpu_to_be32(payload[0]);
+ payload[1] = cpu_to_be32(payload[1]);
+
+ memset(resp, 0, sizeof(resp));
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
+ resp, UHS2_CFG_WRITE_PHY_SET_RESP_LEN);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ if ((resp[2] & 0x80)) {
+ pr_err("%s: %s: UHS2 CMD not accepted, resp= 0x%x!\n",
+ mmc_hostname(host), __func__, resp[2]);
+ return -EIO;
+ }
+
+ /*
+ * Use Control Write CCMD to set LINK/TRAN Setting in Configuration Register.
+ * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
+ * - IOADR = LINK/TRAN Setting Register(CFG_BASE + 00Ch)
+ * - Payload = New contents to be written to LINK/TRAN Setting Register
+ */
+ arg = ((UHS2_DEV_CONFIG_LINK_TRAN_SET & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_WRITE |
+ UHS2_NATIVE_CMD_PLEN_8B |
+ (UHS2_DEV_CONFIG_LINK_TRAN_SET >> 8);
+
+ if (card->uhs2_config.app_type == UHS2_DEV_CONFIG_APP_SD_MEM)
+ card->uhs2_config.maxblk_len_set = UHS2_DEV_CONFIG_LT_SET_MAX_BLK_LEN;
+ else
+ card->uhs2_config.maxblk_len_set = min(card->uhs2_config.maxblk_len,
+ host->uhs2_caps.maxblk_len);
+ host->uhs2_caps.maxblk_len_set = card->uhs2_config.maxblk_len_set;
+
+ card->uhs2_config.n_fcu_set = min(card->uhs2_config.n_fcu, host->uhs2_caps.n_fcu);
+ host->uhs2_caps.n_fcu_set = card->uhs2_config.n_fcu_set;
+
+ card->uhs2_config.n_data_gap_set = max(nMinDataGap, card->uhs2_config.n_data_gap);
+ host->uhs2_caps.n_data_gap_set = card->uhs2_config.n_data_gap_set;
+
+ host->uhs2_caps.max_retry_set = 3;
+ card->uhs2_config.max_retry_set = host->uhs2_caps.max_retry_set;
+
+ payload[0] = (card->uhs2_config.maxblk_len_set << UHS2_DEV_CONFIG_MAX_BLK_LEN_POS) |
+ (card->uhs2_config.max_retry_set << UHS2_DEV_CONFIG_LT_SET_MAX_RETRY_POS) |
+ (card->uhs2_config.n_fcu_set << UHS2_DEV_CONFIG_N_FCU_POS);
+ payload[1] = card->uhs2_config.n_data_gap_set;
+ payload[0] = cpu_to_be32(payload[0]);
+ payload[1] = cpu_to_be32(payload[1]);
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
+ NULL, 0);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /*
+ * Use Control Write CCMD to set Config Completion(payload bit 63) in Generic Setting
+ * Register.
+ * Header:
+ * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
+ * - IOADR = PGeneric Setting Register(CFG_BASE + 008h)
+ * Payload:
+ * - bit [63]: Config Completion
+ *
+ * DLSM transits to Active state immediately when Config Completion is set to 1.
+ */
+ arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_WRITE |
+ UHS2_NATIVE_CMD_PLEN_8B |
+ (UHS2_DEV_CONFIG_GEN_SET >> 8);
+
+ payload[0] = 0;
+ payload[1] = UHS2_DEV_CONFIG_GEN_SET_CFG_COMPLETE;
+ payload[0] = cpu_to_be32(payload[0]);
+ payload[1] = cpu_to_be32(payload[1]);
+
+ memset(resp, 0, sizeof(resp));
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
+ resp, UHS2_CFG_WRITE_GENERIC_SET_RESP_LEN);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /* Set host Config Setting registers */
+ err = host->ops->uhs2_control(host, UHS2_SET_CONFIG);
+ if (err) {
+ pr_err("%s: %s: UHS2 SET_CONFIG fail!\n", mmc_hostname(host), __func__);
+ return err;
+ }
+
+ return 0;
+}
+
+static int sd_uhs2_go_dormant(struct mmc_host *host, u32 node_id)
+{
+ struct mmc_command cmd = {0};
+ struct uhs2_command uhs2_cmd = {};
+ u16 header, arg;
+ __be32 payload[1];
+ int err;
+
+ /* Disable Normal INT */
+ err = host->ops->uhs2_control(host, UHS2_DISABLE_INT);
+ if (err) {
+ pr_err("%s: %s: UHS2 DISABLE_INT fail!\n",
+ mmc_hostname(host), __func__);
+ return err;
+ }
+
+ /*
+ * Refer to UHS-II Addendum Version 1.02 Figure 6-17 to see GO_DORMANT_STATE CCMD format.
+ * Header:
+ * - Control Write(R/W=1) with 4-Byte payload(PLEN=01b).
+ * - IOADR = CMD_BASE + 001h
+ * Payload:
+ * - bit [7]: HBR(Entry to Hibernate Mode)
+ * 1: Host intends to enter Hibernate mode during Dormant state.
+ * The default setting is 0 because hibernate is currently not supported.
+ */
+ header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | node_id;
+ arg = ((UHS2_DEV_CMD_GO_DORMANT_STATE & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_WRITE |
+ UHS2_NATIVE_CMD_PLEN_4B |
+ (UHS2_DEV_CMD_GO_DORMANT_STATE >> 8);
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_GO_DORMANT_PAYLOAD_LEN,
+ NULL, 0);
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err) {
+ pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /* Check Dormant State in Present */
+ err = host->ops->uhs2_control(host, UHS2_CHECK_DORMANT);
+ if (err)
+ return err;
+
+ /* Disable UHS2 card clock */
+ err = host->ops->uhs2_control(host, UHS2_DISABLE_CLK);
+ if (err)
+ return err;
+
+ /* Restore sd clock */
+ mmc_delay(5);
+ err = host->ops->uhs2_control(host, UHS2_ENABLE_CLK);
+ if (err)
+ return err;
+
+ /* Enable Normal INT */
+ err = host->ops->uhs2_control(host, UHS2_ENABLE_INT);
+ if (err)
+ return err;
+
+ /* Detect UHS2 */
+ err = host->ops->uhs2_control(host, UHS2_PHY_INIT);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int __sd_uhs2_wait_active_state_cb(void *cb_data, bool *busy)
+{
+ struct sd_uhs2_wait_active_state_data *data = cb_data;
+ struct mmc_host *host = data->host;
+ struct mmc_command *cmd = data->cmd;
+ int err;
+
+ err = mmc_wait_for_cmd(host, cmd, 0);
+ if (err)
+ return err;
+
+ if (cmd->resp[1] & UHS2_DEV_CONFIG_GEN_SET_CFG_COMPLETE)
+ *busy = false;
+ else
+ *busy = true;
+
return 0;
}
+static int sd_uhs2_change_speed(struct mmc_host *host, u32 node_id)
+{
+ struct mmc_command cmd = {0};
+ struct uhs2_command uhs2_cmd = {};
+ u16 header, arg;
+ int err;
+ struct sd_uhs2_wait_active_state_data cb_data = {
+ .host = host,
+ .cmd = &cmd
+ };
+
+ /* Change Speed Range at controller side. */
+ err = host->ops->uhs2_control(host, UHS2_SET_SPEED_B);
+ if (err) {
+ pr_err("%s: %s: UHS2 SET_SPEED fail!\n", mmc_hostname(host), __func__);
+ return err;
+ }
+
+ err = sd_uhs2_go_dormant(host, node_id);
+ if (err) {
+ pr_err("%s: %s: UHS2 GO_DORMANT_STATE fail, err= 0x%x!\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
+ /*
+ * Use Control Read CCMD to check Config Completion(bit 63) in Generic Setting Register.
+ * - Control Read(R/W=0) with 8-Byte payload(PLEN=10b).
+ * - IOADR = Generic Setting Register(CFG_BASE + 008h)
+ *
+ * When UHS-II card been switched to new speed mode, it will set Config Completion to 1.
+ */
+ header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | node_id;
+ arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
+ UHS2_NATIVE_CMD_READ |
+ UHS2_NATIVE_CMD_PLEN_8B |
+ (UHS2_DEV_CONFIG_GEN_SET >> 8);
+
+ sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, NULL, 0, NULL, 0);
+ err = __mmc_poll_for_busy(host, UHS2_WAIT_CFG_COMPLETE_PERIOD_US,
+ UHS2_WAIT_CFG_COMPLETE_TIMEOUT_MS,
+ &__sd_uhs2_wait_active_state_cb, &cb_data);
+ if (err) {
+ pr_err("%s: %s: Not switch to Active in 100 ms\n", mmc_hostname(host), __func__);
+ return err;
+ }
+
+ return 0;
+}
+
+static int sd_uhs2_get_ro(struct mmc_host *host)
+{
+ /*
+ * Some systems don't feature a write-protect pin and don't need one.
+ * E.g. because they only have micro-SD card slot. For those systems
+ * assume that the SD card is always read-write.
+ */
+ if (host->caps2 & MMC_CAP2_NO_WRITE_PROTECT)
+ return 0;
+
+ if (!host->ops->get_ro)
+ return -1;
+
+ return host->ops->get_ro(host);
+}
+
+/*
+ * Mask off any voltages we don't support and select
+ * the lowest voltage
+ */
+u32 sd_uhs2_select_voltage(struct mmc_host *host, u32 ocr)
+{
+ int bit;
+ int err;
+
+ /*
+ * Sanity check the voltages that the card claims to
+ * support.
+ */
+ if (ocr & 0x7F) {
+ dev_warn(mmc_dev(host), "card claims to support voltages below defined range\n");
+ ocr &= ~0x7F;
+ }
+
+ ocr &= host->ocr_avail;
+ if (!ocr) {
+ dev_warn(mmc_dev(host), "no support for card's volts\n");
+ return 0;
+ }
+
+ if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) {
+ bit = ffs(ocr) - 1;
+ ocr &= 3 << bit;
+ /* Power cycle */
+ err = sd_uhs2_power_off(host);
+ if (err)
+ return 0;
+ err = sd_uhs2_reinit(host);
+ if (err)
+ return 0;
+ } else {
+ bit = fls(ocr) - 1;
+ ocr &= 3 << bit;
+ if (bit != host->ios.vdd)
+ dev_warn(mmc_dev(host), "exceeding card's volts\n");
+ }
+
+ return ocr;
+}
+
/*
* Initialize the UHS-II card through the SD-TRAN transport layer. This enables
* commands/requests to be backwards compatible through the legacy SD protocol.
@@ -107,14 +893,149 @@ static int sd_uhs2_config_write(struct mmc_host *host, struct mmc_card *card)
*/
static int sd_uhs2_legacy_init(struct mmc_host *host, struct mmc_card *card)
{
+ int err;
+ u32 cid[4];
+ u32 ocr;
+ u32 rocr;
+ u8 *status;
+ int ro;
+
+ /* Send CMD0 to reset SD card */
+ err = __mmc_go_idle(host);
+ if (err)
+ return err;
+
+ mmc_delay(1);
+
+ /* Send CMD8 to communicate SD interface operation condition */
+ err = mmc_send_if_cond(host, host->ocr_avail);
+ if (err) {
+ dev_warn(mmc_dev(host), "CMD8 error\n");
+ return err;
+ }
+
+ /*
+ * Probe SD card working voltage.
+ */
+ err = mmc_send_app_op_cond(host, 0, &ocr);
+ if (err)
+ return err;
+
+ card->ocr = ocr;
+
+ /*
+ * Some SD cards claims an out of spec VDD voltage range. Let's treat
+ * these bits as being in-valid and especially also bit7.
+ */
+ ocr &= ~0x7FFF;
+ rocr = sd_uhs2_select_voltage(host, ocr);
+ /*
+ * Some cards have zero value of rocr in UHS-II mode. Assign host's
+ * ocr value to rocr.
+ */
+ if (!rocr)
+ rocr = host->ocr_avail;
+
+ rocr |= (SD_OCR_CCS | SD_OCR_XPC);
+
+ /* Wait SD power on ready */
+ ocr = rocr;
+
+ err = mmc_send_app_op_cond(host, ocr, &rocr);
+ if (err)
+ return err;
+
+ err = mmc_send_cid(host, cid);
+ if (err)
+ return err;
+
+ memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
+ mmc_decode_cid(card);
+
+ /*
+ * For native busses: get card RCA and quit open drain mode.
+ */
+ err = mmc_send_relative_addr(host, &card->rca);
+ if (err)
+ return err;
+
+ err = mmc_sd_get_csd(card);
+ if (err)
+ return err;
+
+ /*
+ * Select card, as all following commands rely on that.
+ */
+ err = mmc_select_card(card);
+ if (err)
+ return err;
+
+ /*
+ * Fetch SCR from card.
+ */
+ err = mmc_app_send_scr(card);
+ if (err)
+ return err;
+
+ err = mmc_decode_scr(card);
+ if (err)
+ return err;
+
+ /*
+ * Switch to high power consumption mode.
+ * Even switch failed, sd card can still work at lower power consumption mode, but
+ * performance will be lower than high power consumption mode.
+ */
+ status = kmalloc(64, GFP_KERNEL);
+ if (!status)
+ return -ENOMEM;
+
+ if (!(card->csd.cmdclass & CCC_SWITCH)) {
+ pr_warn("%s: card lacks mandatory switch function, performance might suffer\n",
+ mmc_hostname(card->host));
+ } else {
+ /* send CMD6 to set Maximum Power Consumption to get better performance */
+ err = mmc_sd_switch(card, 0, 3, SD4_SET_POWER_LIMIT_1_80W, status);
+ if (!err)
+ err = mmc_sd_switch(card, 1, 3, SD4_SET_POWER_LIMIT_1_80W, status);
+
+ err = 0;
+ }
+
+ /*
+ * Check if read-only switch is active.
+ */
+ ro = sd_uhs2_get_ro(host);
+ if (ro < 0) {
+ pr_warn("%s: host does not support read-only switch, assuming write-enable\n",
+ mmc_hostname(host));
+ } else if (ro > 0) {
+ mmc_card_set_readonly(card);
+ }
+
+ /*
+ * NOTE:
+ * Should we read Externsion Register to check power notification feature here?
+ */
+
+ kfree(status);
+
return 0;
}
+static void sd_uhs2_remove(struct mmc_host *host)
+{
+ mmc_remove_card(host->card);
+ host->card = NULL;
+ if (host->flags & MMC_UHS2_INITIALIZED)
+ host->flags &= ~MMC_UHS2_INITIALIZED;
+}
+
/*
* Allocate the data structure for the mmc_card and run the UHS-II specific
* initialization sequence.
*/
-static int sd_uhs2_init_card(struct mmc_host *host)
+static int sd_uhs2_init_card(struct mmc_host *host, struct mmc_card *oldcard)
{
struct mmc_card *card;
u32 node_id;
@@ -128,9 +1049,14 @@ static int sd_uhs2_init_card(struct mmc_host *host)
if (err)
return err;
- card = mmc_alloc_card(host, &sd_type);
- if (IS_ERR(card))
- return PTR_ERR(card);
+ if (oldcard) {
+ card = oldcard;
+ } else {
+ card = mmc_alloc_card(host, &sd_type);
+ if (IS_ERR(card))
+ return PTR_ERR(card);
+ }
+ host->card = card;
card->uhs2_config.node_id = node_id;
card->type = MMC_TYPE_SD;
@@ -143,22 +1069,47 @@ static int sd_uhs2_init_card(struct mmc_host *host)
if (err)
goto err;
+ /* Change to Speed Range B if it is supported */
+ if (card->uhs2_state & MMC_UHS2_SPEED_B) {
+ err = sd_uhs2_change_speed(host, node_id);
+ if (err)
+ return err;
+ }
+
+ host->card->uhs2_state |= MMC_UHS2_INITIALIZED;
+ host->flags |= MMC_UHS2_INITIALIZED;
+
err = sd_uhs2_legacy_init(host, card);
if (err)
goto err;
- host->card = card;
return 0;
err:
- mmc_remove_card(card);
+ if (host->card->uhs2_state & MMC_UHS2_INITIALIZED)
+ host->card->uhs2_state &= ~MMC_UHS2_INITIALIZED;
+ if (host->flags & MMC_UHS2_INITIALIZED)
+ host->flags &= ~MMC_UHS2_INITIALIZED;
+ sd_uhs2_remove(host);
return err;
}
-static void sd_uhs2_remove(struct mmc_host *host)
+int sd_uhs2_reinit(struct mmc_host *host)
{
- mmc_remove_card(host->card);
- host->card = NULL;
+ struct mmc_card *card = host->card;
+ int err;
+
+ sd_uhs2_power_up(host);
+ err = sd_uhs2_phy_init(host);
+ if (err)
+ return err;
+
+ err = sd_uhs2_init_card(host, card);
+ if (err)
+ return err;
+
+ mmc_card_set_present(card);
+ return err;
}
static int sd_uhs2_alive(struct mmc_host *host)
@@ -184,34 +1135,176 @@ static void sd_uhs2_detect(struct mmc_host *host)
}
}
+static int _sd_uhs2_suspend(struct mmc_host *host)
+{
+ struct mmc_card *card = host->card;
+ int err = 0;
+
+ mmc_claim_host(host);
+
+ if (mmc_card_suspended(card))
+ goto out;
+
+ if (mmc_card_can_poweroff_notify(card))
+ err = sd_poweroff_notify(card);
+
+ if (!err) {
+ sd_uhs2_power_off(host);
+ mmc_card_set_suspended(card);
+ }
+
+out:
+ mmc_release_host(host);
+ return err;
+}
+
+/*
+ * Callback for suspend
+ */
static int sd_uhs2_suspend(struct mmc_host *host)
{
- return 0;
+ int err;
+
+ err = _sd_uhs2_suspend(host);
+ if (!err) {
+ pm_runtime_disable(&host->card->dev);
+ pm_runtime_set_suspended(&host->card->dev);
+ }
+
+ return err;
+}
+
+/*
+ * This function tries to determine if the same card is still present
+ * and, if so, restore all state to it.
+ */
+static int _mmc_sd_uhs2_resume(struct mmc_host *host)
+{
+ int err = 0;
+
+ mmc_claim_host(host);
+
+ if (!mmc_card_suspended(host->card))
+ goto out;
+
+ /* Power up UHS2 SD card and re-initialize it. */
+ err = sd_uhs2_reinit(host);
+ mmc_card_clr_suspended(host->card);
+
+out:
+ mmc_release_host(host);
+ return err;
}
+/*
+ * Callback for resume
+ */
static int sd_uhs2_resume(struct mmc_host *host)
{
+ pm_runtime_enable(&host->card->dev);
return 0;
}
+/*
+ * Callback for runtime_suspend.
+ */
static int sd_uhs2_runtime_suspend(struct mmc_host *host)
{
- return 0;
+ int err;
+
+ if (!(host->caps & MMC_CAP_AGGRESSIVE_PM))
+ return 0;
+
+ err = _sd_uhs2_suspend(host);
+ if (err)
+ pr_err("%s: error %d doing aggressive suspend\n", mmc_hostname(host), err);
+
+ return err;
}
static int sd_uhs2_runtime_resume(struct mmc_host *host)
{
- return 0;
+ int err;
+
+ err = _mmc_sd_uhs2_resume(host);
+ if (err && err != -ENOMEDIUM)
+ pr_err("%s: error %d doing runtime resume\n", mmc_hostname(host), err);
+
+ return err;
}
-static int sd_uhs2_shutdown(struct mmc_host *host)
+static int sd_uhs2_hw_reset(struct mmc_host *host)
{
- return 0;
+ int err;
+
+ sd_uhs2_power_off(host);
+ /* Wait at least 1 ms according to SD spec */
+ mmc_delay(1);
+ sd_uhs2_power_up(host);
+
+ err = sd_uhs2_reinit(host);
+
+ return err;
}
-static int sd_uhs2_hw_reset(struct mmc_host *host)
+/*
+ * mmc_uhs2_prepare_cmd - prepare for SD command packet
+ * @host: MMC host
+ * @mrq: MMC request
+ *
+ * Initialize and fill in a header and a payload of SD command packet.
+ * The caller should allocate uhs2_command in host->cmd->uhs2_cmd in
+ * advance.
+ *
+ * Return: 0 on success, non-zero error on failure
+ */
+void mmc_uhs2_prepare_cmd(struct mmc_host *host, struct mmc_request *mrq)
{
- return 0;
+ struct mmc_command *cmd;
+ struct uhs2_command *uhs2_cmd;
+ u16 header, arg;
+ __be32 *payload;
+ u8 plen;
+
+ cmd = mrq->cmd;
+ header = host->card->uhs2_config.node_id;
+ if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC)
+ header |= UHS2_PACKET_TYPE_DCMD;
+ else
+ header |= UHS2_PACKET_TYPE_CCMD;
+
+ arg = cmd->opcode << UHS2_SD_CMD_INDEX_POS;
+ if (host->uhs2_ios.is_APP_CMD) {
+ arg |= UHS2_SD_CMD_APP;
+ host->uhs2_ios.is_APP_CMD = false;
+ }
+
+ uhs2_cmd = cmd->uhs2_cmd;
+ payload = uhs2_cmd->payload;
+ plen = 2; /* at the maximum */
+
+ if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC &&
+ !cmd->uhs2_tmode0_flag) {
+ if (host->uhs2_ios.is_2L_HD_mode)
+ arg |= UHS2_DCMD_2L_HD_MODE;
+
+ arg |= UHS2_DCMD_LM_TLEN_EXIST;
+
+ if (cmd->data->blocks == 1 &&
+ cmd->data->blksz != 512 &&
+ cmd->opcode != MMC_READ_SINGLE_BLOCK &&
+ cmd->opcode != MMC_WRITE_BLOCK) {
+ arg |= UHS2_DCMD_TLUM_BYTE_MODE;
+ payload[1] = cpu_to_be32(cmd->data->blksz);
+ } else {
+ payload[1] = cpu_to_be32(cmd->data->blocks);
+ }
+ } else {
+ plen = 1;
+ }
+
+ payload[0] = cpu_to_be32(cmd->arg);
+ sd_uhs2_cmd_assemble(cmd, uhs2_cmd, header, arg, payload, plen, NULL, 0);
}
static const struct mmc_bus_ops sd_uhs2_ops = {
@@ -222,7 +1315,7 @@ static const struct mmc_bus_ops sd_uhs2_ops = {
.resume = sd_uhs2_resume,
.runtime_suspend = sd_uhs2_runtime_suspend,
.runtime_resume = sd_uhs2_runtime_resume,
- .shutdown = sd_uhs2_shutdown,
+ .shutdown = sd_uhs2_suspend,
.hw_reset = sd_uhs2_hw_reset,
};
@@ -230,6 +1323,8 @@ static int sd_uhs2_attach(struct mmc_host *host)
{
int err;
+ host->flags |= MMC_UHS2_SUPPORT;
+
err = sd_uhs2_power_up(host);
if (err)
goto err;
@@ -238,7 +1333,7 @@ static int sd_uhs2_attach(struct mmc_host *host)
if (err)
goto err;
- err = sd_uhs2_init_card(host);
+ err = sd_uhs2_init_card(host, NULL);
if (err)
goto err;
@@ -251,21 +1346,34 @@ static int sd_uhs2_attach(struct mmc_host *host)
goto remove_card;
mmc_claim_host(host);
+
+ host->ops->uhs2_control(host, UHS2_POST_ATTACH_SD);
+
return 0;
remove_card:
- mmc_remove_card(host->card);
- host->card = NULL;
+ sd_uhs2_remove(host);
mmc_claim_host(host);
- mmc_detach_bus(host);
+
err:
+ mmc_detach_bus(host);
sd_uhs2_power_off(host);
- return err;
+ host->flags &= ~MMC_UHS2_SUPPORT;
+ return 1;
}
+/**
+ * mmc_attach_sd_uhs2 - select UHS2 interface
+ * @host: MMC host
+ *
+ * Try to select UHS2 interface and initialize the bus for a given
+ * frequency, @freq.
+ *
+ * Return: 0 on success, non-zero error on failure
+ */
int mmc_attach_sd_uhs2(struct mmc_host *host)
{
- int i, err = 0;
+ int i, err;
if (!(host->caps2 & MMC_CAP2_SD_UHS2))
return -EOPNOTSUPP;
@@ -280,6 +1388,9 @@ int mmc_attach_sd_uhs2(struct mmc_host *host)
*/
for (i = 0; i < ARRAY_SIZE(sd_uhs2_freqs); i++) {
host->f_init = sd_uhs2_freqs[i];
+ pr_info("%s: %s: trying to init UHS-II card at %u Hz\n",
+ mmc_hostname(host), __func__, host->f_init);
+
err = sd_uhs2_attach(host);
if (!err)
break;
--
2.25.1
This is a UHS-II version of sdhci's set_power operation.
VDD2, as well as VDD, is handled here.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 46 +++++++++++++++++++++++++
drivers/mmc/host/sdhci.c | 63 +++++++++++++++++++----------------
drivers/mmc/host/sdhci.h | 1 +
3 files changed, 82 insertions(+), 28 deletions(-)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index ae862e1eadab..780491781613 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -57,6 +57,13 @@ EXPORT_SYMBOL_GPL(sdhci_uhs2_dump_regs);
* *
\*****************************************************************************/
+static inline int mmc_opt_regulator_set_ocr(struct mmc_host *mmc,
+ struct regulator *supply,
+ unsigned short vdd_bit)
+{
+ return IS_ERR_OR_NULL(supply) ? 0 : mmc_regulator_set_ocr(mmc, supply, vdd_bit);
+}
+
bool sdhci_uhs2_mode(struct sdhci_host *host)
{
return host->mmc->flags & MMC_UHS2_SUPPORT;
@@ -95,6 +102,45 @@ void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask)
}
EXPORT_SYMBOL_GPL(sdhci_uhs2_reset);
+static void sdhci_uhs2_set_power(struct sdhci_host *host, unsigned char mode,
+ unsigned short vdd)
+{
+ struct mmc_host *mmc = host->mmc;
+ u8 pwr;
+
+ if (mode != MMC_POWER_OFF) {
+ pwr = sdhci_get_vdd_value(vdd);
+ if (!pwr)
+ WARN(1, "%s: Invalid vdd %#x\n",
+ mmc_hostname(host->mmc), vdd);
+ pwr |= SDHCI_VDD2_POWER_180;
+ }
+
+ if (host->pwr == pwr)
+ return;
+ host->pwr = pwr;
+
+ if (pwr == 0) {
+ sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
+
+ mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+ mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
+ } else {
+ mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
+ /* support 1.8v only for now */
+ mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, fls(MMC_VDD2_165_195) - 1);
+
+ /* vdd first */
+ pwr |= SDHCI_POWER_ON;
+ sdhci_writeb(host, pwr & 0xf, SDHCI_POWER_CONTROL);
+ mdelay(5);
+
+ pwr |= SDHCI_VDD2_POWER_ON;
+ sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
+ mdelay(5);
+ }
+}
+
/*****************************************************************************\
* *
* Driver init/exit *
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 9b66b9a32c72..99633a3ef549 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -23,7 +23,7 @@
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
-
+#include <linux/bug.h>
#include <linux/leds.h>
#include <linux/mmc/mmc.h>
@@ -2061,41 +2061,48 @@ static void sdhci_set_power_reg(struct sdhci_host *host, unsigned char mode,
sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
}
+unsigned short sdhci_get_vdd_value(unsigned short vdd)
+{
+ u8 pwr;
+
+ switch (1 << vdd) {
+ case MMC_VDD_165_195:
+ /*
+ * Without a regulator, SDHCI does not support 2.0v
+ * so we only get here if the driver deliberately
+ * added the 2.0v range to ocr_avail. Map it to 1.8v
+ * for the purpose of turning on the power.
+ */
+ case MMC_VDD_20_21:
+ pwr = SDHCI_POWER_180;
+ break;
+ case MMC_VDD_29_30:
+ case MMC_VDD_30_31:
+ pwr = SDHCI_POWER_300;
+ break;
+ case MMC_VDD_32_33:
+ case MMC_VDD_33_34:
+ pwr = SDHCI_POWER_330;
+ break;
+ default:
+ pwr = 0;
+ break;
+ }
+
+ return pwr;
+}
+EXPORT_SYMBOL_GPL(sdhci_get_vdd_value);
+
void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
unsigned short vdd)
{
u8 pwr = 0;
if (mode != MMC_POWER_OFF) {
- switch (1 << vdd) {
- case MMC_VDD_165_195:
- /*
- * Without a regulator, SDHCI does not support 2.0v
- * so we only get here if the driver deliberately
- * added the 2.0v range to ocr_avail. Map it to 1.8v
- * for the purpose of turning on the power.
- */
- case MMC_VDD_20_21:
- pwr = SDHCI_POWER_180;
- break;
- case MMC_VDD_29_30:
- case MMC_VDD_30_31:
- pwr = SDHCI_POWER_300;
- break;
- case MMC_VDD_32_33:
- case MMC_VDD_33_34:
- /*
- * 3.4 ~ 3.6V are valid only for those platforms where it's
- * known that the voltage range is supported by hardware.
- */
- case MMC_VDD_34_35:
- case MMC_VDD_35_36:
- pwr = SDHCI_POWER_330;
- break;
- default:
+ pwr = sdhci_get_vdd_value(vdd);
+ if (!pwr) {
WARN(1, "%s: Invalid vdd %#x\n",
mmc_hostname(host->mmc), vdd);
- break;
}
}
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index f41c19c76994..df7fa0c0ebf8 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -840,6 +840,7 @@ void sdhci_set_power(struct sdhci_host *host, unsigned char mode,
void sdhci_set_power_and_bus_voltage(struct sdhci_host *host,
unsigned char mode,
unsigned short vdd);
+unsigned short sdhci_get_vdd_value(unsigned short vdd);
void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
unsigned short vdd);
int sdhci_get_cd_nogpio(struct mmc_host *mmc);
--
2.25.1
For UHS2, the signal voltage is supplied by vdd2 which is already 1.8v,
so no voltage switch required.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 780491781613..2342c05c4cdb 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -141,6 +141,27 @@ static void sdhci_uhs2_set_power(struct sdhci_host *host, unsigned char mode,
}
}
+/*****************************************************************************\
+ * *
+ * MMC callbacks *
+ * *
+\*****************************************************************************/
+
+static int sdhci_uhs2_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ /*
+ * For UHS2, the signal voltage is supplied by vdd2 which is
+ * already 1.8v so no voltage switch required.
+ */
+ if (sdhci_uhs2_mode(host))
+ return 0;
+
+ return sdhci_start_signal_voltage_switch(mmc, ios);
+}
+
/*****************************************************************************\
* *
* Driver init/exit *
@@ -149,6 +170,9 @@ static void sdhci_uhs2_set_power(struct sdhci_host *host, unsigned char mode,
static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
{
+ host->mmc_host_ops.start_signal_voltage_switch =
+ sdhci_uhs2_start_signal_voltage_switch;
+
return 0;
}
--
2.25.1
Sdhci_uhs2_do_detect_init() is a sdhci version of mmc's uhs2_detect_init
operation. After detected, the host's UHS-II capabilities will be set up
here and interrupts will also be enabled.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 117 ++++++++++++++++++++++++++++++++++
1 file changed, 117 insertions(+)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 3d52d35a91a5..52587025d5e3 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -335,6 +335,123 @@ int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
* *
\*****************************************************************************/
+static int sdhci_uhs2_interface_detect(struct sdhci_host *host)
+{
+ /* 100ms */
+ int timeout = 100000;
+ u32 val;
+
+ udelay(200); /* wait for 200us before check */
+
+ if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_IF_DETECT),
+ 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
+ pr_warn("%s: not detect UHS2 interface in 200us.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return -EIO;
+ }
+
+ /* Enable UHS2 error interrupts */
+ sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
+
+ /* 150ms */
+ timeout = 150000;
+ if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_LANE_SYNC),
+ 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
+ pr_warn("%s: UHS2 Lane sync fail in 150ms.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return -EIO;
+ }
+
+ DBG("%s: UHS2 Lane synchronized in UHS2 mode, PHY is initialized.\n",
+ mmc_hostname(host->mmc));
+ return 0;
+}
+
+static int sdhci_uhs2_init(struct sdhci_host *host)
+{
+ u16 caps_ptr = 0;
+ u32 caps_gen = 0;
+ u32 caps_phy = 0;
+ u32 caps_tran[2] = {0, 0};
+ struct mmc_host *mmc = host->mmc;
+
+ caps_ptr = sdhci_readw(host, SDHCI_UHS2_CAPS_PTR);
+ if (caps_ptr < 0x100 || caps_ptr > 0x1FF) {
+ pr_err("%s: SDHCI_UHS2_CAPS_PTR(%d) is wrong.\n",
+ mmc_hostname(mmc), caps_ptr);
+ return -ENODEV;
+ }
+ caps_gen = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_OFFSET);
+ caps_phy = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_PHY_OFFSET);
+ caps_tran[0] = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_TRAN_OFFSET);
+ caps_tran[1] = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_TRAN_1_OFFSET);
+
+ /* General Caps */
+ mmc->uhs2_caps.dap = caps_gen & SDHCI_UHS2_CAPS_DAP_MASK;
+ mmc->uhs2_caps.gap = FIELD_GET(SDHCI_UHS2_CAPS_GAP_MASK, caps_gen);
+ mmc->uhs2_caps.n_lanes = FIELD_GET(SDHCI_UHS2_CAPS_LANE_MASK, caps_gen);
+ mmc->uhs2_caps.addr64 = (caps_gen & SDHCI_UHS2_CAPS_ADDR_64) ? 1 : 0;
+ mmc->uhs2_caps.card_type = FIELD_GET(SDHCI_UHS2_CAPS_DEV_TYPE_MASK, caps_gen);
+
+ /* PHY Caps */
+ mmc->uhs2_caps.phy_rev = caps_phy & SDHCI_UHS2_CAPS_PHY_REV_MASK;
+ mmc->uhs2_caps.speed_range = FIELD_GET(SDHCI_UHS2_CAPS_PHY_RANGE_MASK, caps_phy);
+ mmc->uhs2_caps.n_lss_sync = FIELD_GET(SDHCI_UHS2_CAPS_PHY_N_LSS_SYN_MASK, caps_phy);
+ mmc->uhs2_caps.n_lss_dir = FIELD_GET(SDHCI_UHS2_CAPS_PHY_N_LSS_DIR_MASK, caps_phy);
+ if (mmc->uhs2_caps.n_lss_sync == 0)
+ mmc->uhs2_caps.n_lss_sync = 16 << 2;
+ else
+ mmc->uhs2_caps.n_lss_sync <<= 2;
+ if (mmc->uhs2_caps.n_lss_dir == 0)
+ mmc->uhs2_caps.n_lss_dir = 16 << 3;
+ else
+ mmc->uhs2_caps.n_lss_dir <<= 3;
+
+ /* LINK/TRAN Caps */
+ mmc->uhs2_caps.link_rev = caps_tran[0] & SDHCI_UHS2_CAPS_TRAN_LINK_REV_MASK;
+ mmc->uhs2_caps.n_fcu = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_N_FCU_MASK, caps_tran[0]);
+ if (mmc->uhs2_caps.n_fcu == 0)
+ mmc->uhs2_caps.n_fcu = 256;
+ mmc->uhs2_caps.host_type = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_HOST_TYPE_MASK, caps_tran[0]);
+ mmc->uhs2_caps.maxblk_len = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_BLK_LEN_MASK, caps_tran[0]);
+ mmc->uhs2_caps.n_data_gap = caps_tran[1] & SDHCI_UHS2_CAPS_TRAN_1_N_DATA_GAP_MASK;
+
+ return 0;
+}
+
+static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ int ret = -EIO;
+
+ DBG("Begin do uhs2 detect init.\n");
+
+ if (sdhci_uhs2_interface_detect(host)) {
+ pr_warn("%s: cannot detect UHS2 interface.\n",
+ mmc_hostname(host->mmc));
+ goto out;
+ }
+
+ if (sdhci_uhs2_init(host)) {
+ pr_warn("%s: UHS2 init fail.\n", mmc_hostname(host->mmc));
+ goto out;
+ }
+
+ /* Init complete, do soft reset and enable UHS2 error irqs. */
+ host->ops->uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
+ sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
+ /*
+ * N.B SDHCI_INT_ENABLE and SDHCI_SIGNAL_ENABLE was cleared
+ * by SDHCI_UHS2_SW_RESET_SD
+ */
+ sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+
+ ret = 0;
+out:
+ return ret;
+}
+
static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
{
host->mmc_host_ops.start_signal_voltage_switch =
--
2.25.1
This is a sdhci version of mmc's uhs2_set_reg operation.
UHS-II interface (related registers) will be initialised here.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 92 +++++++++++++++++++++++++++++++++++
drivers/mmc/host/sdhci.c | 11 +++++
2 files changed, 103 insertions(+)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index c7e411fa0a6e..de15a6559702 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -276,6 +276,48 @@ static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
sdhci_set_clock(host, host->clock);
}
+static void sdhci_uhs2_set_config(struct sdhci_host *host)
+{
+ u32 value;
+ u16 sdhci_uhs2_set_ptr = sdhci_readw(host, SDHCI_UHS2_SETTINGS_PTR);
+ u16 sdhci_uhs2_gen_set_reg = (sdhci_uhs2_set_ptr + 0);
+ u16 sdhci_uhs2_phy_set_reg = (sdhci_uhs2_set_ptr + 4);
+ u16 sdhci_uhs2_tran_set_reg = (sdhci_uhs2_set_ptr + 8);
+ u16 sdhci_uhs2_tran_set_1_reg = (sdhci_uhs2_set_ptr + 12);
+
+ /* Set Gen Settings */
+ value = FIELD_PREP(SDHCI_UHS2_GEN_SETTINGS_N_LANES_MASK, host->mmc->uhs2_caps.n_lanes_set);
+ sdhci_writel(host, value, sdhci_uhs2_gen_set_reg);
+
+ /* Set PHY Settings */
+ value = FIELD_PREP(SDHCI_UHS2_PHY_N_LSS_DIR_MASK, host->mmc->uhs2_caps.n_lss_dir_set) |
+ FIELD_PREP(SDHCI_UHS2_PHY_N_LSS_SYN_MASK, host->mmc->uhs2_caps.n_lss_sync_set);
+ if (host->mmc->flags & MMC_UHS2_SPEED_B)
+ value |= SDHCI_UHS2_PHY_SET_SPEED_B;
+ sdhci_writel(host, value, sdhci_uhs2_phy_set_reg);
+
+ /* Set LINK-TRAN Settings */
+ value = FIELD_PREP(SDHCI_UHS2_TRAN_RETRY_CNT_MASK, host->mmc->uhs2_caps.max_retry_set) |
+ FIELD_PREP(SDHCI_UHS2_TRAN_N_FCU_MASK, host->mmc->uhs2_caps.n_fcu_set);
+ sdhci_writel(host, value, sdhci_uhs2_tran_set_reg);
+ sdhci_writel(host, host->mmc->uhs2_caps.n_data_gap_set, sdhci_uhs2_tran_set_1_reg);
+}
+
+static int sdhci_uhs2_check_dormant(struct sdhci_host *host)
+{
+ u32 val;
+ /* 100ms */
+ int timeout = 100000;
+
+ if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_IN_DORMANT_STATE),
+ 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
+ pr_warn("%s: UHS2 IN_DORMANT fail in 100ms.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return -EIO;
+ }
+ return 0;
+}
+
/*****************************************************************************\
* *
* MMC callbacks *
@@ -361,6 +403,55 @@ static int sdhci_uhs2_enable_clk(struct mmc_host *mmc)
return 0;
}
+static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc);
+
+static int sdhci_uhs2_control(struct mmc_host *mmc, enum sd_uhs2_operation op)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ int err = 0;
+ u16 sdhci_uhs2_set_ptr = sdhci_readw(host, SDHCI_UHS2_SETTINGS_PTR);
+ u16 sdhci_uhs2_phy_set_reg = (sdhci_uhs2_set_ptr + 4);
+
+ DBG("Begin uhs2 control, act %d.\n", op);
+
+ switch (op) {
+ case UHS2_PHY_INIT:
+ err = sdhci_uhs2_do_detect_init(mmc);
+ break;
+ case UHS2_SET_CONFIG:
+ sdhci_uhs2_set_config(host);
+ break;
+ case UHS2_ENABLE_INT:
+ sdhci_uhs2_clear_set_irqs(host, 0, SDHCI_INT_CARD_INT);
+ break;
+ case UHS2_DISABLE_INT:
+ sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_CARD_INT, 0);
+ break;
+ case UHS2_SET_SPEED_B:
+ sdhci_writeb(host, SDHCI_UHS2_PHY_SET_SPEED_B, sdhci_uhs2_phy_set_reg);
+ break;
+ case UHS2_CHECK_DORMANT:
+ err = sdhci_uhs2_check_dormant(host);
+ break;
+ case UHS2_DISABLE_CLK:
+ err = sdhci_uhs2_disable_clk(mmc);
+ break;
+ case UHS2_ENABLE_CLK:
+ err = sdhci_uhs2_enable_clk(mmc);
+ break;
+ case UHS2_POST_ATTACH_SD:
+ host->ops->uhs2_post_attach_sd(host);
+ break;
+ default:
+ pr_err("%s: input sd uhs2 operation %d is wrong!\n",
+ mmc_hostname(host->mmc), op);
+ err = -EIO;
+ break;
+ }
+
+ return err;
+}
+
/*****************************************************************************\
* *
* Driver init/exit *
@@ -489,6 +580,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
host->mmc_host_ops.start_signal_voltage_switch =
sdhci_uhs2_start_signal_voltage_switch;
host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
+ host->mmc_host_ops.uhs2_control = sdhci_uhs2_control;
return 0;
}
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 49bbdc155b2b..3656e66fcca7 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -234,6 +234,17 @@ void sdhci_reset(struct sdhci_host *host, u8 mask)
}
EXPORT_SYMBOL_GPL(sdhci_reset);
+void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
+{
+ u32 ier;
+
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ ier &= ~clear;
+ ier |= set;
+ sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+}
+
static bool sdhci_do_reset(struct sdhci_host *host, u8 mask)
{
if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
--
2.25.1
This is a sdhci version of mmc's set_ios operation.
It covers both UHS-I and UHS-II.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 96 +++++++++++++++++++++++++++++++++++
drivers/mmc/host/sdhci-uhs2.h | 1 +
drivers/mmc/host/sdhci.c | 53 +++++++++++--------
drivers/mmc/host/sdhci.h | 2 +
4 files changed, 131 insertions(+), 21 deletions(-)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 00b1b69b49ea..3d52d35a91a5 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -213,6 +213,68 @@ void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
}
EXPORT_SYMBOL_GPL(sdhci_uhs2_set_timeout);
+/**
+ * sdhci_uhs2_clear_set_irqs - set Error Interrupt Status Enable register
+ * @host: SDHCI host
+ * @clear: bit-wise clear mask
+ * @set: bit-wise set mask
+ *
+ * Set/unset bits in UHS-II Error Interrupt Status Enable register
+ */
+void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
+{
+ u32 ier;
+
+ ier = sdhci_readl(host, SDHCI_UHS2_INT_STATUS_ENABLE);
+ ier &= ~clear;
+ ier |= set;
+ sdhci_writel(host, ier, SDHCI_UHS2_INT_STATUS_ENABLE);
+ sdhci_writel(host, ier, SDHCI_UHS2_INT_SIGNAL_ENABLE);
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_clear_set_irqs);
+
+static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u8 cmd_res, dead_lock;
+ u16 ctrl_2;
+
+ /* UHS2 Timeout Control */
+ sdhci_calc_timeout_uhs2(host, &cmd_res, &dead_lock);
+
+ /* change to use calculate value */
+ cmd_res |= FIELD_PREP(SDHCI_UHS2_TIMER_CTRL_DEADLOCK_MASK, dead_lock);
+
+ sdhci_uhs2_clear_set_irqs(host,
+ SDHCI_UHS2_INT_CMD_TIMEOUT |
+ SDHCI_UHS2_INT_DEADLOCK_TIMEOUT,
+ 0);
+ sdhci_writeb(host, cmd_res, SDHCI_UHS2_TIMER_CTRL);
+ sdhci_uhs2_clear_set_irqs(host, 0,
+ SDHCI_UHS2_INT_CMD_TIMEOUT |
+ SDHCI_UHS2_INT_DEADLOCK_TIMEOUT);
+
+ /* UHS2 timing */
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (ios->timing == MMC_TIMING_SD_UHS2)
+ ctrl_2 |= SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE;
+ else
+ ctrl_2 &= ~(SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE);
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+
+ if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
+ sdhci_enable_preset_value(host, true);
+
+ if (host->ops->set_power)
+ host->ops->set_power(host, ios->power_mode, ios->vdd);
+ else
+ sdhci_uhs2_set_power(host, ios->power_mode, ios->vdd);
+ udelay(100);
+
+ host->timing = ios->timing;
+ sdhci_set_clock(host, host->clock);
+}
+
/*****************************************************************************\
* *
* MMC callbacks *
@@ -234,6 +296,39 @@ static int sdhci_uhs2_start_signal_voltage_switch(struct mmc_host *mmc,
return sdhci_start_signal_voltage_switch(mmc, ios);
}
+int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (!(host->version >= SDHCI_SPEC_400) ||
+ !(host->mmc->flags & MMC_UHS2_SUPPORT &&
+ host->mmc->caps2 & MMC_CAP2_SD_UHS2)) {
+ sdhci_set_ios(mmc, ios);
+ return 0;
+ }
+
+ if (ios->power_mode == MMC_POWER_UNDEFINED)
+ return 0;
+
+ if (host->flags & SDHCI_DEVICE_DEAD) {
+ if (!IS_ERR(mmc->supply.vmmc) &&
+ ios->power_mode == MMC_POWER_OFF)
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+ if (!IS_ERR_OR_NULL(mmc->supply.vmmc2) &&
+ ios->power_mode == MMC_POWER_OFF)
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
+ return -1;
+ }
+
+ host->timing = ios->timing;
+
+ sdhci_set_ios_common(mmc, ios);
+
+ __sdhci_uhs2_set_ios(mmc, ios);
+
+ return 0;
+}
+
/*****************************************************************************\
* *
* Driver init/exit *
@@ -244,6 +339,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
{
host->mmc_host_ops.start_signal_voltage_switch =
sdhci_uhs2_start_signal_voltage_switch;
+ host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
return 0;
}
diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
index a58ef19c08aa..184fee80253c 100644
--- a/drivers/mmc/host/sdhci-uhs2.h
+++ b/drivers/mmc/host/sdhci-uhs2.h
@@ -178,5 +178,6 @@ void sdhci_uhs2_dump_regs(struct sdhci_host *host);
bool sdhci_uhs2_mode(struct sdhci_host *host);
void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
+void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
#endif /* __SDHCI_UHS2_H */
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 99633a3ef549..49bbdc155b2b 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -47,8 +47,6 @@
static unsigned int debug_quirks = 0;
static unsigned int debug_quirks2;
-static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
-
static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
void sdhci_dumpregs(struct sdhci_host *host)
@@ -1877,6 +1875,9 @@ static u16 sdhci_get_preset_value(struct sdhci_host *host)
case MMC_TIMING_MMC_HS400:
preset = sdhci_readw(host, SDHCI_PRESET_FOR_HS400);
break;
+ case MMC_TIMING_SD_UHS2:
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_UHS2);
+ break;
default:
pr_warn("%s: Invalid UHS-I mode selected\n",
mmc_hostname(host->mmc));
@@ -2325,24 +2326,9 @@ static bool sdhci_presetable_values_change(struct sdhci_host *host, struct mmc_i
(sdhci_preset_needed(host, ios->timing) || host->drv_type != ios->drv_type);
}
-void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct sdhci_host *host = mmc_priv(mmc);
- bool reinit_uhs = host->reinit_uhs;
- bool turning_on_clk = false;
- u8 ctrl;
-
- host->reinit_uhs = false;
-
- if (ios->power_mode == MMC_POWER_UNDEFINED)
- return;
-
- if (host->flags & SDHCI_DEVICE_DEAD) {
- if (!IS_ERR(mmc->supply.vmmc) &&
- ios->power_mode == MMC_POWER_OFF)
- mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
- return;
- }
/*
* Reset the chip on each power off.
@@ -2359,8 +2345,6 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
sdhci_enable_preset_value(host, false);
if (!ios->clock || ios->clock != host->clock) {
- turning_on_clk = ios->clock && !host->clock;
-
host->ops->set_clock(host, ios->clock);
host->clock = ios->clock;
@@ -2381,6 +2365,32 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
host->ops->set_power(host, ios->power_mode, ios->vdd);
else
sdhci_set_power(host, ios->power_mode, ios->vdd);
+}
+EXPORT_SYMBOL_GPL(sdhci_set_ios_common);
+
+void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ bool reinit_uhs = host->reinit_uhs;
+ bool turning_on_clk = false;
+ u8 ctrl;
+
+ host->reinit_uhs = false;
+
+ if (ios->power_mode == MMC_POWER_UNDEFINED)
+ return;
+
+ if (host->flags & SDHCI_DEVICE_DEAD) {
+ if (!IS_ERR(mmc->supply.vmmc) &&
+ ios->power_mode == MMC_POWER_OFF)
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+ return;
+ }
+
+ sdhci_set_ios_common(mmc, ios);
+
+ if (!ios->clock || ios->clock != host->clock)
+ turning_on_clk = ios->clock && !host->clock;
if (host->ops->platform_send_init_74_clocks)
host->ops->platform_send_init_74_clocks(host, ios->power_mode);
@@ -2959,7 +2969,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
}
EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
-static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
+void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
{
/* Host Controller v3.00 defines preset value registers */
if (host->version < SDHCI_SPEC_300)
@@ -2987,6 +2997,7 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
host->preset_enabled = enable;
}
}
+EXPORT_SYMBOL_GPL(sdhci_enable_preset_value);
static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
int err)
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index df7fa0c0ebf8..c2f989dc2361 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -850,6 +850,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
void sdhci_reset(struct sdhci_host *host, u8 mask);
void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
+void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
+void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios);
void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios);
--
2.25.1
From: Ulf Hansson <[email protected]>
Updates in V4:
- Re-based, updated a comment and removed white-space.
- Moved MMC_VQMMC2_VOLTAGE_180 into a later patch in the series.
Update in previous version:
The SD UHS-II interface was introduced to the SD spec v4.00 several years
ago. The interface is fundamentally different from an electrical and a
protocol point of view, comparing to the legacy SD interface.
However, the legacy SD protocol is supported through a specific transport
layer (SD-TRAN) defined in the UHS-II addendum of the spec. This allows the
SD card to be managed in a very similar way as a legacy SD card, hence a
lot of code can be re-used to support these new types of cards through the
mmc subsystem.
Moreover, an SD card that supports the UHS-II interface shall also be
backwards compatible with the legacy SD interface, which allows a UHS-II
card to be inserted into a legacy slot. As a matter of fact, this is
already supported by mmc subsystem as of today.
To prepare to add support for UHS-II, this change puts the basic foundation
in the mmc core in place, allowing it to be more easily reviewed before
subsequent changes implements the actual support.
Basically, the approach here adds a new UHS-II bus_ops type and adds a
separate initialization path for the UHS-II card. The intent is to avoid us
from sprinkling the legacy initialization path, but also to simplify
implementation of the UHS-II specific bits.
At this point, there is only one new host ops added to manage the various
ios settings needed for UHS-II. Additional host ops that are needed, are
being added from subsequent changes.
Signed-off-by: Ulf Hansson <[email protected]>
---
drivers/mmc/core/Makefile | 2 +-
drivers/mmc/core/core.c | 17 ++-
drivers/mmc/core/core.h | 1 +
drivers/mmc/core/sd_uhs2.c | 289 +++++++++++++++++++++++++++++++++++++
include/linux/mmc/card.h | 7 +
include/linux/mmc/host.h | 19 +++
6 files changed, 333 insertions(+), 2 deletions(-)
create mode 100644 drivers/mmc/core/sd_uhs2.c
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 6a907736cd7a..15b067e8b0d1 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -7,7 +7,7 @@ obj-$(CONFIG_MMC) += mmc_core.o
mmc_core-y := core.o bus.o host.o \
mmc.o mmc_ops.o sd.o sd_ops.o \
sdio.o sdio_ops.o sdio_bus.o \
- sdio_cis.o sdio_io.o sdio_irq.o \
+ sdio_cis.o sdio_io.o sdio_irq.o sd_uhs2.o\
slot-gpio.o regulator.o
mmc_core-$(CONFIG_OF) += pwrseq.o
obj-$(CONFIG_PWRSEQ_SIMPLE) += pwrseq_simple.o
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index c04f5016807b..ad1a40446813 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -2244,6 +2244,18 @@ void mmc_rescan(struct work_struct *work)
goto out;
}
+ /*
+ * Ideally we should favor initialization of legacy SD cards and defer
+ * UHS-II enumeration. However, it seems like cards doesn't reliably
+ * announce their support for UHS-II in the response to the ACMD41,
+ * while initializing the legacy SD interface. Therefore, let's start
+ * with UHS-II for now.
+ */
+ if (!mmc_attach_sd_uhs2(host)) {
+ mmc_release_host(host);
+ goto out;
+ }
+
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
unsigned int freq = freqs[i];
if (freq > host->f_max) {
@@ -2271,10 +2283,13 @@ void mmc_rescan(struct work_struct *work)
void mmc_start_host(struct mmc_host *host)
{
+ bool power_up = !(host->caps2 &
+ (MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_SD_UHS2));
+
host->f_init = max(min(freqs[0], host->f_max), host->f_min);
host->rescan_disable = 0;
- if (!(host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)) {
+ if (power_up) {
mmc_claim_host(host);
mmc_power_up(host, host->ocr_avail);
mmc_release_host(host);
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index 37091a6589ed..920323faa834 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -81,6 +81,7 @@ int mmc_detect_card_removed(struct mmc_host *host);
int mmc_attach_mmc(struct mmc_host *host);
int mmc_attach_sd(struct mmc_host *host);
int mmc_attach_sdio(struct mmc_host *host);
+int mmc_attach_sd_uhs2(struct mmc_host *host);
/* Module parameters */
extern bool use_spi_crc;
diff --git a/drivers/mmc/core/sd_uhs2.c b/drivers/mmc/core/sd_uhs2.c
new file mode 100644
index 000000000000..800957f74632
--- /dev/null
+++ b/drivers/mmc/core/sd_uhs2.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 Linaro Ltd
+ *
+ * Author: Ulf Hansson <[email protected]>
+ *
+ * Support for SD UHS-II cards
+ */
+#include <linux/err.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+
+#include "core.h"
+#include "bus.h"
+#include "sd.h"
+#include "mmc_ops.h"
+
+static const unsigned int sd_uhs2_freqs[] = { 52000000, 26000000 };
+
+static int sd_uhs2_set_ios(struct mmc_host *host)
+{
+ struct mmc_ios *ios = &host->ios;
+
+ return host->ops->uhs2_set_ios(host, ios);
+}
+
+static int sd_uhs2_power_up(struct mmc_host *host)
+{
+ host->ios.vdd = fls(host->ocr_avail) - 1;
+ host->ios.clock = host->f_init;
+ host->ios.timing = MMC_TIMING_SD_UHS2;
+ host->ios.power_mode = MMC_POWER_UP;
+
+ return sd_uhs2_set_ios(host);
+}
+
+static void sd_uhs2_power_off(struct mmc_host *host)
+{
+ host->ios.vdd = 0;
+ host->ios.clock = 0;
+ host->ios.timing = MMC_TIMING_LEGACY;
+ host->ios.power_mode = MMC_POWER_OFF;
+
+ sd_uhs2_set_ios(host);
+}
+
+/*
+ * Run the phy initialization sequence, which mainly relies on the UHS-II host
+ * to check that we reach the expected electrical state, between the host and
+ * the card.
+ */
+static int sd_uhs2_phy_init(struct mmc_host *host)
+{
+ return 0;
+}
+
+/*
+ * Do the early initialization of the card, by sending the device init broadcast
+ * command and wait for the process to be completed.
+ */
+static int sd_uhs2_dev_init(struct mmc_host *host)
+{
+ return 0;
+}
+
+/*
+ * Run the enumeration process by sending the enumerate command to the card.
+ * Note that, we currently support only the point to point connection, which
+ * means only one card can be attached per host/slot.
+ */
+static int sd_uhs2_enum(struct mmc_host *host, u32 *node_id)
+{
+ return 0;
+}
+
+/*
+ * Read the UHS-II configuration registers (CFG_REG) of the card, by sending it
+ * commands and by parsing the responses. Store a copy of the relevant data in
+ * card->uhs2_config.
+ */
+static int sd_uhs2_config_read(struct mmc_host *host, struct mmc_card *card)
+{
+ return 0;
+}
+
+/*
+ * Based on the card's and host's UHS-II capabilities, let's update the
+ * configuration of the card and the host. This may also include to move to a
+ * greater speed range/mode. Depending on the updated configuration, we may need
+ * to do a soft reset of the card via sending it a GO_DORMANT_STATE command.
+ *
+ * In the final step, let's check if the card signals "config completion", which
+ * indicates that the card has moved from config state into active state.
+ */
+static int sd_uhs2_config_write(struct mmc_host *host, struct mmc_card *card)
+{
+ return 0;
+}
+
+/*
+ * Initialize the UHS-II card through the SD-TRAN transport layer. This enables
+ * commands/requests to be backwards compatible through the legacy SD protocol.
+ * UHS-II cards has a specific power limit specified for VDD1/VDD2, that should
+ * be set through a legacy CMD6. Note that, the power limit that becomes set,
+ * survives a soft reset through the GO_DORMANT_STATE command.
+ */
+static int sd_uhs2_legacy_init(struct mmc_host *host, struct mmc_card *card)
+{
+ return 0;
+}
+
+/*
+ * Allocate the data structure for the mmc_card and run the UHS-II specific
+ * initialization sequence.
+ */
+static int sd_uhs2_init_card(struct mmc_host *host)
+{
+ struct mmc_card *card;
+ u32 node_id;
+ int err;
+
+ err = sd_uhs2_dev_init(host);
+ if (err)
+ return err;
+
+ err = sd_uhs2_enum(host, &node_id);
+ if (err)
+ return err;
+
+ card = mmc_alloc_card(host, &sd_type);
+ if (IS_ERR(card))
+ return PTR_ERR(card);
+
+ card->uhs2_config.node_id = node_id;
+ card->type = MMC_TYPE_SD;
+
+ err = sd_uhs2_config_read(host, card);
+ if (err)
+ goto err;
+
+ err = sd_uhs2_config_write(host, card);
+ if (err)
+ goto err;
+
+ err = sd_uhs2_legacy_init(host, card);
+ if (err)
+ goto err;
+
+ host->card = card;
+ return 0;
+
+err:
+ mmc_remove_card(card);
+ return err;
+}
+
+static void sd_uhs2_remove(struct mmc_host *host)
+{
+ mmc_remove_card(host->card);
+ host->card = NULL;
+}
+
+static int sd_uhs2_alive(struct mmc_host *host)
+{
+ return mmc_send_status(host->card, NULL);
+}
+
+static void sd_uhs2_detect(struct mmc_host *host)
+{
+ int err;
+
+ mmc_get_card(host->card, NULL);
+ err = _mmc_detect_card_removed(host);
+ mmc_put_card(host->card, NULL);
+
+ if (err) {
+ sd_uhs2_remove(host);
+
+ mmc_claim_host(host);
+ mmc_detach_bus(host);
+ sd_uhs2_power_off(host);
+ mmc_release_host(host);
+ }
+}
+
+static int sd_uhs2_suspend(struct mmc_host *host)
+{
+ return 0;
+}
+
+static int sd_uhs2_resume(struct mmc_host *host)
+{
+ return 0;
+}
+
+static int sd_uhs2_runtime_suspend(struct mmc_host *host)
+{
+ return 0;
+}
+
+static int sd_uhs2_runtime_resume(struct mmc_host *host)
+{
+ return 0;
+}
+
+static int sd_uhs2_shutdown(struct mmc_host *host)
+{
+ return 0;
+}
+
+static int sd_uhs2_hw_reset(struct mmc_host *host)
+{
+ return 0;
+}
+
+static const struct mmc_bus_ops sd_uhs2_ops = {
+ .remove = sd_uhs2_remove,
+ .alive = sd_uhs2_alive,
+ .detect = sd_uhs2_detect,
+ .suspend = sd_uhs2_suspend,
+ .resume = sd_uhs2_resume,
+ .runtime_suspend = sd_uhs2_runtime_suspend,
+ .runtime_resume = sd_uhs2_runtime_resume,
+ .shutdown = sd_uhs2_shutdown,
+ .hw_reset = sd_uhs2_hw_reset,
+};
+
+static int sd_uhs2_attach(struct mmc_host *host)
+{
+ int err;
+
+ err = sd_uhs2_power_up(host);
+ if (err)
+ goto err;
+
+ err = sd_uhs2_phy_init(host);
+ if (err)
+ goto err;
+
+ err = sd_uhs2_init_card(host);
+ if (err)
+ goto err;
+
+ mmc_attach_bus(host, &sd_uhs2_ops);
+
+ mmc_release_host(host);
+
+ err = mmc_add_card(host->card);
+ if (err)
+ goto remove_card;
+
+ mmc_claim_host(host);
+ return 0;
+
+remove_card:
+ mmc_remove_card(host->card);
+ host->card = NULL;
+ mmc_claim_host(host);
+ mmc_detach_bus(host);
+err:
+ sd_uhs2_power_off(host);
+ return err;
+}
+
+int mmc_attach_sd_uhs2(struct mmc_host *host)
+{
+ int i, err = 0;
+
+ if (!(host->caps2 & MMC_CAP2_SD_UHS2))
+ return -EOPNOTSUPP;
+
+ /* Turn off the legacy SD interface before trying with UHS-II. */
+ mmc_power_off(host);
+
+ /*
+ * Start UHS-II initialization at 52MHz and possibly make a retry at
+ * 26MHz according to the spec. It's required that the host driver
+ * validates ios->clock, to set a rate within the correct range.
+ */
+ for (i = 0; i < ARRAY_SIZE(sd_uhs2_freqs); i++) {
+ host->f_init = sd_uhs2_freqs[i];
+ err = sd_uhs2_attach(host);
+ if (!err)
+ break;
+ }
+
+ return err;
+}
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index c726ea781255..4a42f31b7bb0 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -211,6 +211,11 @@ struct sd_ext_reg {
#define SD_EXT_PERF_CMD_QUEUE (1<<4)
};
+struct sd_uhs2_config {
+ u32 node_id;
+ /* TODO: Extend with more register configs. */
+};
+
struct sdio_cccr {
unsigned int sdio_vsn;
unsigned int sd_vsn;
@@ -317,6 +322,8 @@ struct mmc_card {
struct sd_ext_reg ext_power; /* SD extension reg for PM */
struct sd_ext_reg ext_perf; /* SD extension reg for PERF */
+ struct sd_uhs2_config uhs2_config; /* SD UHS-II config */
+
unsigned int sdio_funcs; /* number of SDIO functions */
atomic_t sdio_funcs_probed; /* number of probed SDIO funcs */
struct sdio_cccr cccr; /* common card info */
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 8fdd3cf971a3..150d10d5e6f8 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -63,6 +63,7 @@ struct mmc_ios {
#define MMC_TIMING_MMC_HS400 10
#define MMC_TIMING_SD_EXP 11
#define MMC_TIMING_SD_EXP_1_2V 12
+#define MMC_TIMING_SD_UHS2 13
unsigned char signal_voltage; /* signalling voltage (1.8V or 3.3V) */
@@ -91,6 +92,10 @@ struct mmc_clk_phase_map {
struct mmc_clk_phase phase[MMC_NUM_CLK_PHASES];
};
+struct sd_uhs2_caps {
+ /* TODO: Add UHS-II capabilities for the host. */
+};
+
struct mmc_host;
enum mmc_err_stat {
@@ -145,6 +150,17 @@ struct mmc_host_ops {
*/
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
+ /*
+ * The uhs2_set_ios callback is mandatory to implement for hosts that
+ * supports the SD UHS-II interface (MMC_CAP2_SD_UHS2), while the
+ * callback is unused for the other cases. Note that, the struct
+ * mmc_ios is being re-used for this as well.
+ *
+ * Expected return values for the uhs2_set_ios callback are a negative
+ * errno in case of a failure or zero for success.
+ */
+ int (*uhs2_set_ios)(struct mmc_host *host, struct mmc_ios *ios);
+
/*
* Return values for the get_ro callback should be:
* 0 for a read/write card
@@ -396,6 +412,7 @@ struct mmc_host {
MMC_CAP2_HS200_1_2V_SDR)
#define MMC_CAP2_SD_EXP (1 << 7) /* SD express via PCIe */
#define MMC_CAP2_SD_EXP_1_2V (1 << 8) /* SD express 1.2V */
+#define MMC_CAP2_SD_UHS2 (1 << 9) /* SD UHS-II support */
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */
@@ -422,6 +439,8 @@ struct mmc_host {
#endif
#define MMC_CAP2_ALT_GPT_TEGRA (1 << 28) /* Host with eMMC that has GPT entry at a non-standard location */
+ struct sd_uhs2_caps uhs2_caps; /* Host UHS-II capabilities */
+
int fixed_drv_type; /* fixed driver type for non-removable media */
mmc_pm_flag_t pm_caps; /* supported pm features */
--
2.25.1
From: Victor Shih <[email protected]>
This "pre" hook for detect_init(), uhs2_pre_detect_init, will be required
to enable UHS-II support, at least, on GL9755.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 3 +++
drivers/mmc/host/sdhci.h | 1 +
2 files changed, 4 insertions(+)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 5349173b4f90..1b481260885f 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -1358,6 +1358,9 @@ static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc)
DBG("Begin do uhs2 detect init.\n");
+ if (host->ops && host->ops->uhs2_pre_detect_init)
+ host->ops->uhs2_pre_detect_init(host);
+
if (sdhci_uhs2_interface_detect(host)) {
pr_warn("%s: cannot detect UHS2 interface.\n",
mmc_hostname(host->mmc));
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 5f090b5ecc61..e5f64b38e339 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -727,6 +727,7 @@ struct sdhci_ops {
struct mmc_request *mrq);
void (*dump_vendor_regs)(struct sdhci_host *host);
void (*dump_uhs2_regs)(struct sdhci_host *host);
+ void (*uhs2_pre_detect_init)(struct sdhci_host *host);
};
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
--
2.25.1
Changes are:
* Disable GL9755 overcurrent interrupt when power on/off on UHS-II.
* Enable the internal clock when do reset on UHS-II mode.
* Set ZC to 0x0 for Sandisk cards and set ZC to 0xB for others.
* Increase timeout value before detecting UHS-II interface.
* Add vendor settings fro UHS-II mode.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/Kconfig | 1 +
drivers/mmc/host/sdhci-pci-gli.c | 309 ++++++++++++++++++++++++++++++-
2 files changed, 309 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 9c22370fb778..e68cdcb72cba 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -111,6 +111,7 @@ config MMC_SDHCI_PCI
tristate "SDHCI support on PCI bus"
depends on MMC_SDHCI && PCI
select MMC_CQHCI
+ select MMC_SDHCI_UHS2
select IOSF_MBI if X86
select MMC_SDHCI_IO_ACCESSORS
help
diff --git a/drivers/mmc/host/sdhci-pci-gli.c b/drivers/mmc/host/sdhci-pci-gli.c
index 633a8ee8f8c5..384f47ed15d9 100644
--- a/drivers/mmc/host/sdhci-pci-gli.c
+++ b/drivers/mmc/host/sdhci-pci-gli.c
@@ -18,6 +18,7 @@
#include "sdhci-cqhci.h"
#include "sdhci-pci.h"
#include "cqhci.h"
+#include "sdhci-uhs2.h"
/* Genesys Logic extra registers */
#define SDHCI_GLI_9750_WT 0x800
@@ -139,9 +140,36 @@
#define PCI_GLI_9755_PLLSSC 0x68
#define PCI_GLI_9755_PLLSSC_PPM GENMASK(15, 0)
+#define PCI_GLI_9755_PLLSSC_RTL BIT(24)
+#define GLI_9755_PLLSSC_RTL_VALUE 0x1
+#define PCI_GLI_9755_PLLSSC_TRANS_PASS BIT(27)
+#define GLI_9755_PLLSSC_TRANS_PASS_VALUE 0x1
+#define PCI_GLI_9755_PLLSSC_RECV GENMASK(29, 28)
+#define GLI_9755_PLLSSC_RECV_VALUE 0x3
+#define PCI_GLI_9755_PLLSSC_TRAN GENMASK(31, 30)
+#define GLI_9755_PLLSSC_TRAN_VALUE 0x3
+
+#define PCI_GLI_9755_UHS2_PLL 0x6C
+#define PCI_GLI_9755_UHS2_PLL_SSC GENMASK(9, 8)
+#define GLI_9755_UHS2_PLL_SSC_VALUE 0x0
+#define PCI_GLI_9755_UHS2_PLL_DELAY BIT(18)
+#define GLI_9755_UHS2_PLL_DELAY_VALUE 0x1
+#define PCI_GLI_9755_UHS2_PLL_PDRST BIT(27)
+#define GLI_9755_UHS2_PLL_PDRST_VALUE 0x1
#define PCI_GLI_9755_SerDes 0x70
+#define PCI_GLI_9755_UHS2_SERDES_INTR GENMASK(2, 0)
+#define GLI_9755_UHS2_SERDES_INTR_VALUE 0x3
+#define PCI_GLI_9755_UHS2_SERDES_ZC1 BIT(3)
+#define GLI_9755_UHS2_SERDES_ZC1_VALUE 0x0
+#define PCI_GLI_9755_UHS2_SERDES_ZC2 GENMASK(7, 4)
+#define GLI_9755_UHS2_SERDES_ZC2_DEFAULT 0xB
+#define GLI_9755_UHS2_SERDES_ZC2_SANDISK 0x0
#define PCI_GLI_9755_SCP_DIS BIT(19)
+#define PCI_GLI_9755_UHS2_SERDES_TRAN GENMASK(27, 24)
+#define GLI_9755_UHS2_SERDES_TRAN_VALUE 0xC
+#define PCI_GLI_9755_UHS2_SERDES_RECV GENMASK(31, 28)
+#define GLI_9755_UHS2_SERDES_RECV_VALUE 0xF
#define PCI_GLI_9755_MISC 0x78
#define PCI_GLI_9755_MISC_SSC_OFF BIT(26)
@@ -693,6 +721,276 @@ static void gl9755_hw_setting(struct sdhci_pci_slot *slot)
gl9755_wt_off(pdev);
}
+static void gl9755_vendor_init(struct sdhci_host *host)
+{
+ struct sdhci_pci_slot *slot = sdhci_priv(host);
+ struct pci_dev *pdev = slot->chip->pdev;
+ u32 serdes;
+ u32 pllssc;
+ u32 uhs2_pll;
+
+ gl9755_wt_on(pdev);
+
+ pci_read_config_dword(pdev, PCI_GLI_9755_SerDes, &serdes);
+ serdes &= ~PCI_GLI_9755_UHS2_SERDES_TRAN;
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_TRAN,
+ GLI_9755_UHS2_SERDES_TRAN_VALUE);
+ serdes &= ~PCI_GLI_9755_UHS2_SERDES_RECV;
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_RECV,
+ GLI_9755_UHS2_SERDES_RECV_VALUE);
+ serdes &= ~PCI_GLI_9755_UHS2_SERDES_INTR;
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_INTR,
+ GLI_9755_UHS2_SERDES_INTR_VALUE);
+ serdes &= ~PCI_GLI_9755_UHS2_SERDES_ZC1;
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC1,
+ GLI_9755_UHS2_SERDES_ZC1_VALUE);
+ serdes &= ~PCI_GLI_9755_UHS2_SERDES_ZC2;
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC2,
+ GLI_9755_UHS2_SERDES_ZC2_DEFAULT);
+ pci_write_config_dword(pdev, PCI_GLI_9755_SerDes, serdes);
+
+ pci_read_config_dword(pdev, PCI_GLI_9755_UHS2_PLL, &uhs2_pll);
+ uhs2_pll &= ~PCI_GLI_9755_UHS2_PLL_SSC;
+ uhs2_pll |= FIELD_PREP(PCI_GLI_9755_UHS2_PLL_SSC,
+ GLI_9755_UHS2_PLL_SSC_VALUE);
+ uhs2_pll &= ~PCI_GLI_9755_UHS2_PLL_DELAY;
+ uhs2_pll |= FIELD_PREP(PCI_GLI_9755_UHS2_PLL_DELAY,
+ GLI_9755_UHS2_PLL_DELAY_VALUE);
+ uhs2_pll &= ~PCI_GLI_9755_UHS2_PLL_PDRST;
+ uhs2_pll |= FIELD_PREP(PCI_GLI_9755_UHS2_PLL_PDRST,
+ GLI_9755_UHS2_PLL_PDRST_VALUE);
+ pci_write_config_dword(pdev, PCI_GLI_9755_UHS2_PLL, uhs2_pll);
+
+ pci_read_config_dword(pdev, PCI_GLI_9755_PLLSSC, &pllssc);
+ pllssc &= ~PCI_GLI_9755_PLLSSC_RTL;
+ pllssc |= FIELD_PREP(PCI_GLI_9755_PLLSSC_RTL,
+ GLI_9755_PLLSSC_RTL_VALUE);
+ pllssc &= ~PCI_GLI_9755_PLLSSC_TRANS_PASS;
+ pllssc |= FIELD_PREP(PCI_GLI_9755_PLLSSC_TRANS_PASS,
+ GLI_9755_PLLSSC_TRANS_PASS_VALUE);
+ pllssc &= ~PCI_GLI_9755_PLLSSC_RECV;
+ pllssc |= FIELD_PREP(PCI_GLI_9755_PLLSSC_RECV,
+ GLI_9755_PLLSSC_RECV_VALUE);
+ pllssc &= ~PCI_GLI_9755_PLLSSC_TRAN;
+ pllssc |= FIELD_PREP(PCI_GLI_9755_PLLSSC_TRAN,
+ GLI_9755_PLLSSC_TRAN_VALUE);
+ pci_write_config_dword(pdev, PCI_GLI_9755_PLLSSC, pllssc);
+
+ gl9755_wt_off(pdev);
+}
+
+static void gl9755_pre_detect_init(struct sdhci_host *host)
+{
+ /* GL9755 need more time on UHS2 detect flow */
+ sdhci_writeb(host, 0xA7, SDHCI_UHS2_TIMER_CTRL);
+}
+
+static void gl9755_post_attach_sd(struct sdhci_host *host)
+{
+ struct pci_dev *pdev;
+ struct sdhci_pci_chip *chip;
+ struct sdhci_pci_slot *slot;
+ u32 serdes;
+
+ slot = sdhci_priv(host);
+ chip = slot->chip;
+ pdev = chip->pdev;
+
+ gl9755_wt_on(pdev);
+
+ pci_read_config_dword(pdev, PCI_GLI_9755_SerDes, &serdes);
+ serdes &= ~PCI_GLI_9755_UHS2_SERDES_ZC1;
+ serdes &= ~PCI_GLI_9755_UHS2_SERDES_ZC2;
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC1,
+ GLI_9755_UHS2_SERDES_ZC1_VALUE);
+
+ /* the manfid of sandisk card is 0x3 */
+ if (host->mmc->card->cid.manfid == 0x3)
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC2,
+ GLI_9755_UHS2_SERDES_ZC2_SANDISK);
+ else
+ serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC2,
+ GLI_9755_UHS2_SERDES_ZC2_DEFAULT);
+
+ pci_write_config_dword(pdev, PCI_GLI_9755_SerDes, serdes);
+
+ gl9755_wt_off(pdev);
+}
+
+static void gl9755_overcurrent_event_enable(struct sdhci_host *host,
+ bool enable)
+{
+ u32 mask;
+
+ mask = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
+ if (enable)
+ mask |= SDHCI_INT_BUS_POWER;
+ else
+ mask &= ~SDHCI_INT_BUS_POWER;
+
+ sdhci_writel(host, mask, SDHCI_SIGNAL_ENABLE);
+
+ mask = sdhci_readl(host, SDHCI_INT_ENABLE);
+ if (enable)
+ mask |= SDHCI_INT_BUS_POWER;
+ else
+ mask &= ~SDHCI_INT_BUS_POWER;
+
+ sdhci_writel(host, mask, SDHCI_INT_ENABLE);
+}
+
+static void gl9755_set_power(struct sdhci_host *host, unsigned char mode,
+ unsigned short vdd)
+{
+ u8 pwr = 0;
+
+ if (mode != MMC_POWER_OFF) {
+ switch (1 << vdd) {
+ case MMC_VDD_165_195:
+ /*
+ * Without a regulator, SDHCI does not support 2.0v
+ * so we only get here if the driver deliberately
+ * added the 2.0v range to ocr_avail. Map it to 1.8v
+ * for the purpose of turning on the power.
+ */
+ case MMC_VDD_20_21:
+ pwr = SDHCI_POWER_180;
+ break;
+ case MMC_VDD_29_30:
+ case MMC_VDD_30_31:
+ pwr = SDHCI_POWER_300;
+ break;
+ case MMC_VDD_32_33:
+ case MMC_VDD_33_34:
+ pwr = SDHCI_POWER_330;
+ break;
+ default:
+ WARN(1, "%s: Invalid vdd %#x\n",
+ mmc_hostname(host->mmc), vdd);
+ break;
+ }
+
+ pwr |= SDHCI_VDD2_POWER_180;
+ }
+
+ if (host->pwr == pwr)
+ return;
+
+ host->pwr = pwr;
+
+ if (pwr == 0) {
+ gl9755_overcurrent_event_enable(host, false);
+ sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
+ } else {
+ gl9755_overcurrent_event_enable(host, false);
+ sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
+
+ pwr |= (SDHCI_POWER_ON | SDHCI_VDD2_POWER_ON);
+
+ sdhci_writeb(host, pwr & 0xf, SDHCI_POWER_CONTROL);
+ /* wait stable */
+ mdelay(5);
+ sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
+ /* wait stable */
+ mdelay(5);
+ gl9755_overcurrent_event_enable(host, true);
+ }
+}
+
+static bool sdhci_wait_clock_stable(struct sdhci_host *host)
+{
+ u16 clk = 0;
+ ktime_t timeout;
+
+ /* Wait max 20 ms */
+ timeout = ktime_add_ms(ktime_get(), 20);
+ while (1) {
+ bool timedout = ktime_after(ktime_get(), timeout);
+
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ if (clk & SDHCI_CLOCK_INT_STABLE)
+ break;
+ if (timedout) {
+ pr_err("%s: Internal clock never stabilised.\n",
+ mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return false;
+ }
+ udelay(10);
+ }
+ return true;
+}
+
+static void gl9755_uhs2_reset_sd_tran(struct sdhci_host *host)
+{
+ /* do this on UHS2 mode */
+ if (host->mmc->flags & MMC_UHS2_INITIALIZED) {
+ sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
+ sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+ sdhci_uhs2_clear_set_irqs(host,
+ SDHCI_INT_ALL_MASK,
+ SDHCI_UHS2_INT_ERROR_MASK);
+ }
+}
+
+static void sdhci_gl9755_reset(struct sdhci_host *host, u8 mask)
+{
+ ktime_t timeout;
+ u16 ctrl2;
+ u16 clk_ctrl;
+
+ /* need internal clock */
+ if (mask & SDHCI_RESET_ALL) {
+ ctrl2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ clk_ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+
+ if ((ctrl2 & SDHCI_CTRL_V4_MODE) &&
+ (ctrl2 & SDHCI_CTRL_UHS2_ENABLE)) {
+ sdhci_writew(host,
+ SDHCI_CLOCK_INT_EN,
+ SDHCI_CLOCK_CONTROL);
+ } else {
+ sdhci_writew(host,
+ SDHCI_CLOCK_INT_EN,
+ SDHCI_CLOCK_CONTROL);
+ sdhci_wait_clock_stable(host);
+ sdhci_writew(host,
+ SDHCI_CTRL_V4_MODE,
+ SDHCI_HOST_CONTROL2);
+ }
+ }
+
+ sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET);
+
+ /* reset sd-tran on UHS2 mode if need to reset cmd/data */
+ if ((mask & SDHCI_RESET_CMD) | (mask & SDHCI_RESET_DATA))
+ gl9755_uhs2_reset_sd_tran(host);
+
+ if (mask & SDHCI_RESET_ALL)
+ host->clock = 0;
+
+ /* Wait max 100 ms */
+ timeout = ktime_add_ms(ktime_get(), 100);
+
+ /* hw clears the bit when it's done */
+ while (1) {
+ bool timedout = ktime_after(ktime_get(), timeout);
+
+ if (!(sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask))
+ break;
+ if (timedout) {
+ pr_err("%s: Reset 0x%x never completed.\n",
+ mmc_hostname(host->mmc), (int)mask);
+ sdhci_dumpregs(host);
+ /* manual clear */
+ sdhci_writeb(host, 0, SDHCI_SOFTWARE_RESET);
+ return;
+ }
+ udelay(10);
+ }
+}
+
static int gli_probe_slot_gl9750(struct sdhci_pci_slot *slot)
{
struct sdhci_host *host = slot->host;
@@ -713,6 +1011,7 @@ static int gli_probe_slot_gl9755(struct sdhci_pci_slot *slot)
gli_pcie_enable_msi(slot);
slot->host->mmc->caps2 |= MMC_CAP2_NO_SDIO;
sdhci_enable_v4_mode(host);
+ gl9755_vendor_init(host);
return 0;
}
@@ -1085,17 +1384,25 @@ static const struct sdhci_ops sdhci_gl9755_ops = {
.read_w = sdhci_gli_readw,
.read_b = sdhci_gli_readb,
.set_clock = sdhci_gl9755_set_clock,
+ .set_power = gl9755_set_power,
.enable_dma = sdhci_pci_enable_dma,
.set_bus_width = sdhci_set_bus_width,
- .reset = sdhci_reset,
+ .reset = sdhci_gl9755_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
.voltage_switch = sdhci_gli_voltage_switch,
+ .dump_uhs2_regs = sdhci_uhs2_dump_regs,
+ .set_timeout = sdhci_uhs2_set_timeout,
+ .irq = sdhci_uhs2_irq,
+ .uhs2_pre_detect_init = gl9755_pre_detect_init,
+ .uhs2_post_attach_sd = gl9755_post_attach_sd,
};
const struct sdhci_pci_fixes sdhci_gl9755 = {
.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
.quirks2 = SDHCI_QUIRK2_BROKEN_DDR50,
.probe_slot = gli_probe_slot_gl9755,
+ .add_host = sdhci_pci_uhs2_add_host,
+ .remove_host = sdhci_pci_uhs2_remove_host,
.ops = &sdhci_gl9755_ops,
#ifdef CONFIG_PM_SLEEP
.resume = sdhci_pci_gli_resume,
--
2.25.1
From: Ulf Hansson <[email protected]>
Updates in V4:
- Moved the voltage defines into this patch.
Update in previous version:
To allow an additional external regulator to be controlled by an mmc host
driver, let's add support for a vqmmc2 regulator to the mmc core.
For an SD UHS-II interface the vqmmc2 regulator may correspond to the so
called vdd2 supply, as described by the SD spec. Initially, only 1.8V is
needed, hence limit the new helper function, mmc_regulator_set_vqmmc2() to
this too.
Note that, to allow for flexibility mmc host drivers need to manage the
enable/disable of the vqmmc2 regulator themselves, while the regulator is
looked up through the common mmc_regulator_get_supply().
Signed-off-by: Ulf Hansson <[email protected]>
---
drivers/mmc/core/regulator.c | 34 ++++++++++++++++++++++++++++++++++
include/linux/mmc/host.h | 11 +++++++++++
2 files changed, 45 insertions(+)
diff --git a/drivers/mmc/core/regulator.c b/drivers/mmc/core/regulator.c
index 609201a467ef..3c189682797c 100644
--- a/drivers/mmc/core/regulator.c
+++ b/drivers/mmc/core/regulator.c
@@ -223,6 +223,33 @@ int mmc_regulator_set_vqmmc(struct mmc_host *mmc, struct mmc_ios *ios)
}
EXPORT_SYMBOL_GPL(mmc_regulator_set_vqmmc);
+/**
+ * mmc_regulator_set_vqmmc2 - Set vqmmc2 as per the ios->vqmmc2_voltage
+ * @mmc: The mmc host to regulate
+ * @ios: The io bus settings
+ *
+ * Sets a new voltage level for the vqmmc2 regulator, which may correspond to
+ * the vdd2 regulator for an SD UHS-II interface. This function is expected to
+ * be called by mmc host drivers.
+ *
+ * Returns a negative error code on failure, zero if the voltage level was
+ * changed successfully or a positive value if the level didn't need to change.
+ */
+int mmc_regulator_set_vqmmc2(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ if (IS_ERR(mmc->supply.vqmmc2))
+ return -EINVAL;
+
+ switch (ios->vqmmc2_voltage) {
+ case MMC_VQMMC2_VOLTAGE_180:
+ return mmc_regulator_set_voltage_if_supported(
+ mmc->supply.vqmmc2, 1700000, 1800000, 1950000);
+ default:
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL_GPL(mmc_regulator_set_vqmmc2);
+
#else
static inline int mmc_regulator_get_ocrmask(struct regulator *supply)
@@ -249,6 +276,7 @@ int mmc_regulator_get_supply(struct mmc_host *mmc)
mmc->supply.vmmc = devm_regulator_get_optional(dev, "vmmc");
mmc->supply.vqmmc = devm_regulator_get_optional(dev, "vqmmc");
+ mmc->supply.vqmmc2 = devm_regulator_get_optional(dev, "vqmmc2");
if (IS_ERR(mmc->supply.vmmc)) {
if (PTR_ERR(mmc->supply.vmmc) == -EPROBE_DEFER)
@@ -268,6 +296,12 @@ int mmc_regulator_get_supply(struct mmc_host *mmc)
dev_dbg(dev, "No vqmmc regulator found\n");
}
+ if (IS_ERR(mmc->supply.vqmmc2)) {
+ if (PTR_ERR(mmc->supply.vqmmc2) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_dbg(dev, "No vqmmc2 regulator found\n");
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(mmc_regulator_get_supply);
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 150d10d5e6f8..895bcf7f80b7 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -71,6 +71,9 @@ struct mmc_ios {
#define MMC_SIGNAL_VOLTAGE_180 1
#define MMC_SIGNAL_VOLTAGE_120 2
+ unsigned char vqmmc2_voltage;
+#define MMC_VQMMC2_VOLTAGE_180 0
+
unsigned char drv_type; /* driver type (A, B, C, D) */
#define MMC_SET_DRIVER_TYPE_B 0
@@ -321,6 +324,7 @@ struct mmc_pwrseq;
struct mmc_supply {
struct regulator *vmmc; /* Card power supply */
struct regulator *vqmmc; /* Optional Vccq supply */
+ struct regulator *vqmmc2; /* Optional supply for phy */
};
struct mmc_ctx {
@@ -600,6 +604,7 @@ int mmc_regulator_set_ocr(struct mmc_host *mmc,
struct regulator *supply,
unsigned short vdd_bit);
int mmc_regulator_set_vqmmc(struct mmc_host *mmc, struct mmc_ios *ios);
+int mmc_regulator_set_vqmmc2(struct mmc_host *mmc, struct mmc_ios *ios);
#else
static inline int mmc_regulator_set_ocr(struct mmc_host *mmc,
struct regulator *supply,
@@ -613,6 +618,12 @@ static inline int mmc_regulator_set_vqmmc(struct mmc_host *mmc,
{
return -EINVAL;
}
+
+static inline int mmc_regulator_set_vqmmc2(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ return -EINVAL;
+}
#endif
int mmc_regulator_get_supply(struct mmc_host *mmc);
--
2.25.1
This patch adds sdhci-uhs2.c as a module for UHS-II support.
This is a skelton for further development in this patch series.
This kernel configuration, CONFIG_MMC_SDHCI_UHS2, will be used
in the following commits to indicate UHS-II specific code in sdhci
controllers.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/Kconfig | 9 +++++++
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/sdhci-uhs2.c | 46 +++++++++++++++++++++++++++++++++++
3 files changed, 56 insertions(+)
create mode 100644 drivers/mmc/host/sdhci-uhs2.c
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5e19a961c34d..9c22370fb778 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -98,6 +98,15 @@ config MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
This is the case for the Nintendo Wii SDHCI.
+config MMC_SDHCI_UHS2
+ tristate "UHS2 support on SDHCI controller"
+ depends on MMC_SDHCI
+ help
+ This option is selected by SDHCI controller drivers that want to
+ support UHS2-capable devices.
+
+ If you have a controller with this feature, say Y or M here.
+
config MMC_SDHCI_PCI
tristate "SDHCI support on PCI bus"
depends on MMC_SDHCI && PCI
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index ba0c6d0cd85d..ac1dd27fca3a 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_MMC_PXA) += pxamci.o
obj-$(CONFIG_MMC_MXC) += mxcmmc.o
obj-$(CONFIG_MMC_MXS) += mxs-mmc.o
obj-$(CONFIG_MMC_SDHCI) += sdhci.o
+obj-$(CONFIG_MMC_SDHCI_UHS2) += sdhci-uhs2.o
obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o
sdhci-pci-y += sdhci-pci-core.o sdhci-pci-o2micro.o sdhci-pci-arasan.o \
sdhci-pci-dwc-mshc.o sdhci-pci-gli.o
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
new file mode 100644
index 000000000000..8e15bd0dadee
--- /dev/null
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/mmc/host/sdhci_uhs2.c - Secure Digital Host Controller
+ * Interface driver
+ *
+ * Copyright (C) 2014 Intel Corp, All Rights Reserved.
+ * Copyright (C) 2020 Genesys Logic, Inc.
+ * Authors: Ben Chuang <[email protected]>
+ * Copyright (C) 2020 Linaro Limited
+ * Author: AKASHI Takahiro <[email protected]>
+ */
+
+#include <linux/module.h>
+
+#include "sdhci.h"
+#include "sdhci-uhs2.h"
+
+#define DRIVER_NAME "sdhci_uhs2"
+#define DBG(f, x...) \
+ pr_debug(DRIVER_NAME " [%s()]: " f, __func__, ## x)
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
+{
+ return 0;
+}
+
+static int __init sdhci_uhs2_mod_init(void)
+{
+ return 0;
+}
+module_init(sdhci_uhs2_mod_init);
+
+static void __exit sdhci_uhs2_mod_exit(void)
+{
+}
+module_exit(sdhci_uhs2_mod_exit);
+
+MODULE_AUTHOR("Intel, Genesys Logic, Linaro");
+MODULE_DESCRIPTION("MMC UHS-II Support");
+MODULE_LICENSE("GPL v2");
--
2.25.1
This is a UHS-II version of sdhci's add_host/remove_host operation.
Any sdhci drivers which are capable of handling UHS-II cards must
call those functions instead of the corresponding sdhci's.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 171 ++++++++++++++++++++++++++++++++++
drivers/mmc/host/sdhci-uhs2.h | 2 +
drivers/mmc/host/sdhci.c | 28 ++++--
drivers/mmc/host/sdhci.h | 12 +++
4 files changed, 204 insertions(+), 9 deletions(-)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 769cf88b21cf..5349173b4f90 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -17,6 +17,7 @@
#include <linux/ktime.h>
#include <linux/mmc/mmc.h>
#include <linux/dmaengine.h>
+#include <linux/regulator/consumer.h>
#include "sdhci.h"
#include "sdhci-uhs2.h"
@@ -1015,6 +1016,176 @@ static irqreturn_t sdhci_uhs2_thread_irq(int irq, void *dev_id)
return IRQ_HANDLED;
}
+/*****************************************************************************\
+ *
+ * Device allocation/registration *
+ * *
+\*****************************************************************************/
+
+static int __sdhci_uhs2_add_host_v4(struct sdhci_host *host, u32 caps1)
+{
+ struct mmc_host *mmc;
+ u32 max_current_caps2;
+
+ if (host->version < SDHCI_SPEC_400)
+ return 0;
+
+ mmc = host->mmc;
+
+ /* Support UHS2 */
+ if (caps1 & SDHCI_SUPPORT_UHS2)
+ mmc->caps2 |= MMC_CAP2_SD_UHS2;
+
+ max_current_caps2 = sdhci_readl(host, SDHCI_MAX_CURRENT_1);
+
+ if ((caps1 & SDHCI_CAN_VDD2_180) &&
+ !max_current_caps2 &&
+ !IS_ERR(mmc->supply.vmmc2)) {
+ /* UHS2 - VDD2 */
+ int curr = regulator_get_current_limit(mmc->supply.vmmc2);
+
+ if (curr > 0) {
+ /* convert to SDHCI_MAX_CURRENT format */
+ curr = curr / 1000; /* convert to mA */
+ curr = curr / SDHCI_MAX_CURRENT_MULTIPLIER;
+ curr = min_t(u32, curr, SDHCI_MAX_CURRENT_LIMIT);
+ max_current_caps2 = curr;
+ }
+ }
+
+ if (caps1 & SDHCI_CAN_VDD2_180) {
+ mmc->ocr_avail_uhs2 |= MMC_VDD2_165_195;
+ /*
+ * UHS2 doesn't require this. Only UHS-I bus needs to set
+ * max current.
+ */
+ mmc->max_current_180_vdd2 = (max_current_caps2 &
+ SDHCI_MAX_CURRENT_VDD2_180_MASK) *
+ SDHCI_MAX_CURRENT_MULTIPLIER;
+ } else {
+ mmc->caps2 &= ~MMC_CAP2_SD_UHS2;
+ }
+
+ return 0;
+}
+
+static int sdhci_uhs2_host_ops_init(struct sdhci_host *host);
+
+static int __sdhci_uhs2_add_host(struct sdhci_host *host)
+{
+ unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
+ struct mmc_host *mmc = host->mmc;
+ int ret;
+
+ if ((mmc->caps2 & MMC_CAP2_CQE) &&
+ (host->quirks & SDHCI_QUIRK_BROKEN_CQE)) {
+ mmc->caps2 &= ~MMC_CAP2_CQE;
+ mmc->cqe_ops = NULL;
+ }
+
+ host->complete_wq = alloc_workqueue("sdhci", flags, 0);
+ if (!host->complete_wq)
+ return -ENOMEM;
+
+ INIT_WORK(&host->complete_work, host->complete_work_fn);
+
+ timer_setup(&host->timer, sdhci_timeout_timer, 0);
+ timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
+
+ init_waitqueue_head(&host->buf_ready_int);
+
+ sdhci_init(host, 0);
+
+ ret = request_threaded_irq(host->irq, sdhci_irq,
+ host->thread_irq_fn,
+ IRQF_SHARED, mmc_hostname(mmc), host);
+ if (ret) {
+ pr_err("%s: Failed to request IRQ %d: %d\n",
+ mmc_hostname(mmc), host->irq, ret);
+ goto unwq;
+ }
+
+ ret = mmc_add_host(mmc);
+ if (ret)
+ return 1;
+
+ pr_info("%s: SDHCI controller on %s [%s] using %s\n",
+ mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
+ host->use_external_dma ? "External DMA" :
+ (host->flags & SDHCI_USE_ADMA) ?
+ (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" :
+ (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");
+
+ sdhci_enable_card_detection(host);
+
+ return 0;
+
+unwq:
+ destroy_workqueue(host->complete_wq);
+
+ return ret;
+}
+
+static void __sdhci_uhs2_remove_host(struct sdhci_host *host, int dead)
+{
+ if (!sdhci_uhs2_mode(host))
+ return;
+
+ if (!dead)
+ sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_FULL);
+}
+
+int sdhci_uhs2_add_host(struct sdhci_host *host)
+{
+ struct mmc_host *mmc = host->mmc;
+ int ret;
+
+ ret = sdhci_setup_host(host);
+ if (ret)
+ return ret;
+
+ if (host->version >= SDHCI_SPEC_400) {
+ ret = __sdhci_uhs2_add_host_v4(host, host->caps1);
+ if (ret)
+ goto cleanup;
+ }
+
+ if ((mmc->caps2 & MMC_CAP2_SD_UHS2) && !host->v4_mode)
+ /* host doesn't want to enable UHS2 support */
+ /* FIXME: Do we have to do some cleanup here? */
+ mmc->caps2 &= ~MMC_CAP2_SD_UHS2;
+
+ /* overwrite ops */
+ if (mmc->caps2 & MMC_CAP2_SD_UHS2)
+ sdhci_uhs2_host_ops_init(host);
+
+ host->complete_work_fn = sdhci_uhs2_complete_work;
+ host->thread_irq_fn = sdhci_uhs2_thread_irq;
+
+ ret = __sdhci_uhs2_add_host(host);
+ if (ret)
+ goto cleanup2;
+
+ return 0;
+
+cleanup2:
+ if (host->version >= SDHCI_SPEC_400)
+ __sdhci_uhs2_remove_host(host, 0);
+cleanup:
+ sdhci_cleanup_host(host);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_add_host);
+
+void sdhci_uhs2_remove_host(struct sdhci_host *host, int dead)
+{
+ __sdhci_uhs2_remove_host(host, dead);
+
+ sdhci_remove_host(host, dead);
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_remove_host);
+
void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host = mmc_priv(mmc);
diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
index 64b7f356c9fc..b2048c48d056 100644
--- a/drivers/mmc/host/sdhci-uhs2.h
+++ b/drivers/mmc/host/sdhci-uhs2.h
@@ -182,5 +182,7 @@ void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq);
int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq);
u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask);
+int sdhci_uhs2_add_host(struct sdhci_host *host);
+void sdhci_uhs2_remove_host(struct sdhci_host *host, int dead);
#endif /* __SDHCI_UHS2_H */
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 6c67b79102eb..82ad35f4d1b4 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -175,10 +175,11 @@ static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
}
-static void sdhci_enable_card_detection(struct sdhci_host *host)
+void sdhci_enable_card_detection(struct sdhci_host *host)
{
sdhci_set_card_detection(host, true);
}
+EXPORT_SYMBOL_GPL(sdhci_enable_card_detection);
static void sdhci_disable_card_detection(struct sdhci_host *host)
{
@@ -369,7 +370,7 @@ static void sdhci_config_dma(struct sdhci_host *host)
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
}
-static void sdhci_init(struct sdhci_host *host, int soft)
+void sdhci_init(struct sdhci_host *host, int soft)
{
struct mmc_host *mmc = host->mmc;
unsigned long flags;
@@ -395,6 +396,7 @@ static void sdhci_init(struct sdhci_host *host, int soft)
mmc->ops->set_ios(mmc, &mmc->ios);
}
}
+EXPORT_SYMBOL_GPL(sdhci_init);
static void sdhci_reinit(struct sdhci_host *host)
{
@@ -458,7 +460,7 @@ static void sdhci_led_control(struct led_classdev *led,
spin_unlock_irqrestore(&host->lock, flags);
}
-static int sdhci_led_register(struct sdhci_host *host)
+int sdhci_led_register(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;
@@ -475,14 +477,16 @@ static int sdhci_led_register(struct sdhci_host *host)
return led_classdev_register(mmc_dev(mmc), &host->led);
}
+EXPORT_SYMBOL_GPL(sdhci_led_register);
-static void sdhci_led_unregister(struct sdhci_host *host)
+void sdhci_led_unregister(struct sdhci_host *host)
{
if (host->quirks & SDHCI_QUIRK_NO_LED)
return;
led_classdev_unregister(&host->led);
}
+EXPORT_SYMBOL_GPL(sdhci_led_unregister);
static inline void sdhci_led_activate(struct sdhci_host *host)
{
@@ -3265,7 +3269,7 @@ void sdhci_complete_work(struct work_struct *work)
}
EXPORT_SYMBOL_GPL(sdhci_complete_work);
-static void sdhci_timeout_timer(struct timer_list *t)
+void sdhci_timeout_timer(struct timer_list *t)
{
struct sdhci_host *host;
unsigned long flags;
@@ -3286,8 +3290,9 @@ static void sdhci_timeout_timer(struct timer_list *t)
spin_unlock_irqrestore(&host->lock, flags);
}
+EXPORT_SYMBOL_GPL(sdhci_timeout_timer);
-static void sdhci_timeout_data_timer(struct timer_list *t)
+void sdhci_timeout_data_timer(struct timer_list *t)
{
struct sdhci_host *host;
unsigned long flags;
@@ -3318,6 +3323,7 @@ static void sdhci_timeout_data_timer(struct timer_list *t)
spin_unlock_irqrestore(&host->lock, flags);
}
+EXPORT_SYMBOL_GPL(sdhci_timeout_data_timer);
/*****************************************************************************\
* *
@@ -3577,7 +3583,7 @@ static inline bool sdhci_defer_done(struct sdhci_host *host,
data->host_cookie == COOKIE_MAPPED);
}
-static irqreturn_t sdhci_irq(int irq, void *dev_id)
+irqreturn_t sdhci_irq(int irq, void *dev_id)
{
struct mmc_request *mrqs_done[SDHCI_MAX_MRQS] = {0};
irqreturn_t result = IRQ_NONE;
@@ -3718,6 +3724,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
return result;
}
+EXPORT_SYMBOL_GPL(sdhci_irq);
irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
{
@@ -4122,6 +4129,9 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,
host->max_timeout_count = 0xE;
+ host->complete_work_fn = sdhci_complete_work;
+ host->thread_irq_fn = sdhci_thread_irq;
+
return host;
}
@@ -4874,7 +4884,7 @@ int __sdhci_add_host(struct sdhci_host *host)
if (!host->complete_wq)
return -ENOMEM;
- INIT_WORK(&host->complete_work, sdhci_complete_work);
+ INIT_WORK(&host->complete_work, host->complete_work_fn);
timer_setup(&host->timer, sdhci_timeout_timer, 0);
timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
@@ -4883,7 +4893,7 @@ int __sdhci_add_host(struct sdhci_host *host)
sdhci_init(host, 0);
- ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
+ ret = request_threaded_irq(host->irq, sdhci_irq, host->thread_irq_fn,
IRQF_SHARED, mmc_hostname(mmc), host);
if (ret) {
pr_err("%s: Failed to request IRQ %d: %d\n",
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 4979af7cabca..5f090b5ecc61 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -629,6 +629,9 @@ struct sdhci_host {
struct timer_list timer; /* Timer for timeouts */
struct timer_list data_timer; /* Timer for data timeouts */
+ void (*complete_work_fn)(struct work_struct *work);
+ irqreturn_t (*thread_irq_fn)(int irq, void *dev_id);
+
#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA)
struct dma_chan *rx_chan;
struct dma_chan *tx_chan;
@@ -832,6 +835,12 @@ static inline void sdhci_read_caps(struct sdhci_host *host)
}
bool sdhci_data_line_cmd(struct mmc_command *cmd);
+void sdhci_enable_card_detection(struct sdhci_host *host);
+void sdhci_init(struct sdhci_host *host, int soft);
+#if IS_REACHABLE(CONFIG_LEDS_CLASS)
+int sdhci_led_register(struct sdhci_host *host);
+void sdhci_led_unregister(struct sdhci_host *host);
+#endif
void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, unsigned long timeout);
void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data);
void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data);
@@ -875,6 +884,9 @@ int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq);
void sdhci_complete_work(struct work_struct *work);
+void sdhci_timeout_timer(struct timer_list *t);
+void sdhci_timeout_data_timer(struct timer_list *t);
+irqreturn_t sdhci_irq(int irq, void *dev_id);
irqreturn_t sdhci_thread_irq(int irq, void *dev_id);
void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
dma_addr_t addr, int len, unsigned int cmd);
--
2.25.1
From: Ben Chuang <[email protected]>
This "post" hook for mmc_attach_sd(), uhs2_post_attach_sd, will be required
to enable UHS-II support, at least, on GL9755.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
---
drivers/mmc/host/sdhci.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index e5f64b38e339..4aac22b58d79 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -728,6 +728,7 @@ struct sdhci_ops {
void (*dump_vendor_regs)(struct sdhci_host *host);
void (*dump_uhs2_regs)(struct sdhci_host *host);
void (*uhs2_pre_detect_init)(struct sdhci_host *host);
+ void (*uhs2_post_attach_sd)(struct sdhci_host *host);
};
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
--
2.25.1
This is a sdhci version of mmc's uhs2_[enable|disable]_clk operations.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 52587025d5e3..c7e411fa0a6e 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -14,6 +14,7 @@
#include <linux/module.h>
#include <linux/iopoll.h>
#include <linux/bitfield.h>
+#include <linux/ktime.h>
#include "sdhci.h"
#include "sdhci-uhs2.h"
@@ -329,6 +330,37 @@ int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
return 0;
}
+static int sdhci_uhs2_disable_clk(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u16 clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+
+ clk &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ return 0;
+}
+
+static int sdhci_uhs2_enable_clk(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u16 clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ u32 val;
+ /* 20ms */
+ int timeout_us = 20000;
+
+ clk |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ if (read_poll_timeout_atomic(sdhci_readw, val, (val & SDHCI_CLOCK_INT_STABLE),
+ 10, timeout_us, true, host, SDHCI_CLOCK_CONTROL)) {
+ pr_err("%s: Internal clock never stabilised.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return 1;
+ }
+ return 0;
+}
+
/*****************************************************************************\
* *
* Driver init/exit *
--
2.25.1
Sdhci_uhs2_reset() does a UHS-II specific reset operation.
Signed-off-by: Ben Chuang <[email protected]>
Signed-off-by: AKASHI Takahiro <[email protected]>
Signed-off-by: Victor Shih <[email protected]>
---
drivers/mmc/host/sdhci-uhs2.c | 46 +++++++++++++++++++++++++++++++++++
drivers/mmc/host/sdhci-uhs2.h | 2 ++
2 files changed, 48 insertions(+)
diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
index 7ea15f06fa99..ae862e1eadab 100644
--- a/drivers/mmc/host/sdhci-uhs2.c
+++ b/drivers/mmc/host/sdhci-uhs2.c
@@ -10,7 +10,9 @@
* Author: AKASHI Takahiro <[email protected]>
*/
+#include <linux/delay.h>
#include <linux/module.h>
+#include <linux/iopoll.h>
#include "sdhci.h"
#include "sdhci-uhs2.h"
@@ -49,6 +51,50 @@ void sdhci_uhs2_dump_regs(struct sdhci_host *host)
}
EXPORT_SYMBOL_GPL(sdhci_uhs2_dump_regs);
+/*****************************************************************************\
+ * *
+ * Low level functions *
+ * *
+\*****************************************************************************/
+
+bool sdhci_uhs2_mode(struct sdhci_host *host)
+{
+ return host->mmc->flags & MMC_UHS2_SUPPORT;
+}
+
+/**
+ * sdhci_uhs2_reset - invoke SW reset
+ * @host: SDHCI host
+ * @mask: Control mask
+ *
+ * Invoke SW reset, depending on a bit in @mask and wait for completion.
+ */
+void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask)
+{
+ unsigned long timeout;
+ u32 val;
+
+ sdhci_writew(host, mask, SDHCI_UHS2_SW_RESET);
+
+ if (mask & SDHCI_UHS2_SW_RESET_FULL)
+ host->clock = 0;
+
+ /* Wait max 100 ms */
+ timeout = 100000;
+
+ /* hw clears the bit when it's done */
+ if (read_poll_timeout_atomic(sdhci_readw, val, !(val & mask), 10,
+ timeout, true, host, SDHCI_UHS2_SW_RESET)) {
+ pr_err("%s: %s: Reset 0x%x never completed.\n",
+ __func__, mmc_hostname(host->mmc), (int)mask);
+ pr_err("%s: clean reset bit\n",
+ mmc_hostname(host->mmc));
+ sdhci_writeb(host, 0, SDHCI_UHS2_SW_RESET);
+ return;
+ }
+}
+EXPORT_SYMBOL_GPL(sdhci_uhs2_reset);
+
/*****************************************************************************\
* *
* Driver init/exit *
diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
index a1fcd7899829..03b9f6675cdc 100644
--- a/drivers/mmc/host/sdhci-uhs2.h
+++ b/drivers/mmc/host/sdhci-uhs2.h
@@ -175,5 +175,7 @@
struct sdhci_host;
void sdhci_uhs2_dump_regs(struct sdhci_host *host);
+bool sdhci_uhs2_mode(struct sdhci_host *host);
+void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
#endif /* __SDHCI_UHS2_H */
--
2.25.1
Hi, Ulf and Adrian
Excuse me for disturbing your nice vacation, if you already have a
closer look at
a paragraph at the series after you finish the vacation, could you let
me know your comments first, let me check it first.
Thanks, Victor Shih
On Tue, Dec 13, 2022 at 5:01 PM Victor Shih <[email protected]> wrote:
>
> Summary
> =======
> These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
>
> About UHS-II, roughly deal with the following three parts:
> 1) A UHS-II detection and initialization:
> - Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
> [2]).
> - Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
> - In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
> 3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
> Setup Sequence.
>
> 2) Send Legacy SD command through SD-TRAN
> - Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
> compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
> Types and Format Overview[3]).
> - Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
> CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
>
> 3) UHS-II Interrupt
> - Except for UHS-II error interrupts, most interrupts share the original
> interrupt registers.
>
> Patch structure
> ===============
> patch#1-#6: for core
> patch#7-#23: for sdhci
> patch#24: for GL9755
>
> Changes in v6 (Dec. 12, 2022)
> * rebased to the linux-kernel-v6.1.0-rc8 in Ulf Hansson next branch.
> * according to the guidance and overall architecture provided
> by Ulf Hansson, Ben Chuang and Jason Lai to implement the
> UHS-2 Core function based on the patches of the [V4,0/6]
> Preparations to support SD UHS-II cards[5].
> * according to the guidance and comments provided by
> Adrian Hunter, Ben Chuang and AKASHI Takahiro to implement
> the UHS-2 Host function based on the patches of the
> [RFC,v3.1,00/27] Add support UHS-II for GL9755[4].
> * implement the necessary function to let the UHS-2 Core/Host
> work properly.
> * fix most of checkpatch warnings/errors.
> * according to the guidance and comments provided by
> Adrian Hunter, Ben Chuang to implement the UHS-2
> Host function based on the patches of the
> [V5,00/26] Add support UHS-II for GL9755[6].
> * The uhs2_post_attach_sd() has implemented in Patch#6 and
> Patch#17 so drop the V5 version of the Patch#23.
> * Modifies the usage of the flags used by the sdhci host for
> MMC_UHS2_INITIALIZED.
> * Patch#5: Drop unused definitions and functions.
> * Patch#7: Rename definitions.
> Use BIT() GENMASK() in some cases.
> * Patch#8: Merge V5 version of Patch[7] and Patch[9] into
> V6 version of Patch[8].
> * Patch#9: Drop unnecessary function.
> Rename used definitions.
> * Patch#10: Drop unnecessary function and simplify some code.
> * Patch#11: Drop unnecessary function.
> Add new mmc_opt_regulator_set_ocr function.
> * Patch#13: Drop unnecessary function.
> Use GENMASK() and FIELD_PREP() in some cases.
> * Patch#14: Drop unnecessary function.
> Modify return value in some function.
> Use GENMASK() and FIELD_PREP() in some cases.
> * Patch#15: Drop unnecessary function.
> Rename used definitions.
> Use GENMASK() and FIELD_GET() in some cases.
> Wrap at 100 columns in some functions.
> * Patch#16: Drop unnecessary function.
> * Patch#17: Drop unnecessary function.
> Drop the unnecessary parameter when call the DBG()
> function.
> Rename used definitions.
> Cancel the export state of some functions.
> Use GENMASK() and FIELD_PREP() in some cases.
> * Patch#18: Drop unnecessary function.
> Add uhs2_dev_cmd function to simplify some functions.
> Rename used definitions.
> Cancel the export state of some functions.
> Use GENMASK() and FIELD_PREP() in some cases.
> * Patch#19: Drop unnecessary function.
> Add sdhci_uhs2_mode() in some functions.
> Rename used definitions.
> Cancel the export state of some functions.
> * Patch#20: Add new complete_work_fn/thread_irq_fn variables in
> struct sdhci_host.
> Use complete_work_fn/thread_irq_fn variables in
> sdhci_alloc_host()/sdhci_uhs2_add_host().
> Rename used definitions.
> * Patch[24]: Rename used definitions.
>
> Reference
> =========
> [1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
> [2] SD Host Controller Simplified Specification 4.20
> [3] UHS-II Simplified Addendum 1.02
> [4] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> [5] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> [6] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
>
> ----------------- original cover letter from v5 -----------------
> Summary
> =======
> These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
>
> About UHS-II, roughly deal with the following three parts:
> 1) A UHS-II detection and initialization:
> - Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
> [2]).
> - Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
> - In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
> 3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
> Setup Sequence.
>
> 2) Send Legacy SD command through SD-TRAN
> - Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
> compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
> Types and Format Overview[3]).
> - Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
> CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
>
> 3) UHS-II Interrupt
> - Except for UHS-II error interrupts, most interrupts share the original
> interrupt registers.
>
> Patch structure
> ===============
> patch#1-#6: for core
> patch#7-#25: for sdhci
> patch#26: for GL9755
>
> Changes in v5 (Oct. 19, 2022)
> * rebased to the linux-kernel-v6.1-rc1 in Ulf Hansson next branch.
> * according to the guidance and overall architecture provided
> by Ulf Hansson, Ben Chuang and Jason Lai to implement the
> UHS-2 Core function based on the patches of the [V4,0/6]
> Preparations to support SD UHS-II cards[5].
> * according to the guidance and comments provided by
> Adrian Hunter, Ben Chuang and AKASHI Takahiro to implement
> the UHS-2 Host function based on the patches of the
> [RFC,v3.1,00/27] Add support UHS-II for GL9755[4].
> * implement the necessary function to let the UHS-2 Core/Host
> work properly.
> * fix most of checkpatch warnings/errors
>
> Reference
> =========
> [1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
> [2] SD Host Controller Simplified Specification 4.20
> [3] UHS-II Simplified Addendum 1.02
> [4] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> [5] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
>
> ----------------- original cover letter from v3.1 -----------------
> This is an interim snapshot of our next version, v4, for enabling
> UHS-II on MMC/SD.
>
> It is focused on 'sdhci' side to address Adrian's comments regarding
> "modularising" sdhci-uhs2.c.
> The whole aim of this version is to get early feedback from Adrian (and
> others) on this issue. Without any consensus about the code structure,
> it would make little sense to go further ahead on sdhci side.
> (Actually, Adrian has made no comments other than "modularising" so far.)
>
> I heavily reworked/refactored sdhci-uhs2.c and re-organised the patch
> set to meet what I believe Adrian expects; no UHS-II related code in
> Legacy (UHS-I) code or sdhci.c.
>
> Nevertheless, almost of all changes I made are trivial and straightforward
> in this direction, and I believe that there is no logic changed since v3
> except sdhci_uhs2_irq(), as ops->irq hook, where we must deal with UHS-II
> command sequences in addition to UHS-II errors. So I added extra handlings.
>
> I admit that there is plenty of room for improvements (for example,
> handling host->flags), but again the focal point here is how sdhci-uhs2.c
> should be built as a module.
>
> Please review this series (particularly Patch#8-#26 and #27) from this
> viewpoint in the first place.
> (Ben is working on 'host' side but there is no change on 'host' side
> in this submission except a minor tweak.)
>
> Thanks,
> -Takahiro Akashi
>
> ------ original cover letter from v3 ------
> Summary
> =======
> These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
>
> About UHS-II, roughly deal with the following three parts:
> 1) A UHS-II detection and initialization:
> - Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
> [2]).
> - Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
> - In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
> 3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
> Setup Sequence.
>
> 2) Send Legacy SD command through SD-TRAN
> - Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
> compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
> Types and Format Overview[3]).
> - Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
> CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
>
> 3) UHS-II Interrupt
> - Except for UHS-II error interrupts, most interrupts share the original
> interrupt registers.
>
> Patch structure
> ===============
> patch#1-#7: for core
> patch#8-#17: for sdhci
> patch#18-#21: for GL9755
>
> Tests
> =====
> Ran 'dd' command to evaluate the performance:
> (SanDisk UHS-II card on GL9755 controller)
> Read Write
> UHS-II disabled (UHS-I): 88.3MB/s 60.7MB/s
> UHS-II enabled : 206MB/s 80MB/s
>
> TODO
> ====
> - replace some define with BIT macro
>
> Reference
> =========
> [1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
> [2] SD Host Controller Simplified Specification 4.20
> [3] UHS-II Simplified Addendum 1.02
>
> Changes in v3 (Jul. 10, 2020)
> * rebased to v5.8-rc4
> * add copyright notice
> * reorganize the patch set and split some commits into smaller ones
> * separate uhs-2 headers from others
> * correct wrong spellings
> * fix most of checkpatch warnings/errors
> * remove all k[cz]alloc() from the code
> * guard sdhci-uhs2 specific code with
> 'if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2))'
> * make sdhci-uhs2.c as a module
> * trivial changes, including
> - rename back sdhci-core.c to sdhci.c
> - allow vendor code to disable uhs2 if v4_mode == 0
> in __sdhci_add_host()
> - merge uhs2_power_up() into mmc_power_up()
> - remove flag_uhs2 from mmc_attach_sd()
> - add function descriptions to EXPORT'ed functions
> - other minor code optimization
>
> Changes in v2 (Jan. 9, 2020)
> * rebased to v5.5-rc5
>
> AKASHI Takahiro (1):
> mmc: sdhci-pci: add UHS-II support framework
>
> Ben Chuang (1):
> mmc: sdhci-uhs2: add post-mmc_attach_sd hook
>
> Ulf Hansson (3):
> mmc: core: Prepare to support SD UHS-II cards
> mmc: core: Announce successful insertion of an SD UHS-II card
> mmc: core: Extend support for mmc regulators with a vqmmc2
>
> Victor Shih (19):
> mmc: core: Cleanup printing of speed mode at card insertion
> mmc: core: Add definitions for SD UHS-II cards
> mmc: core: Support UHS-II card control and access
> mmc: sdhci: add UHS-II related definitions in headers
> mmc: sdhci: add UHS-II module and add a kernel configuration
> mmc: sdhci-uhs2: dump UHS-II registers
> mmc: sdhci-uhs2: add reset function and uhs2_mode function
> mmc: sdhci-uhs2: add set_power() to support vdd2
> mmc: sdhci-uhs2: skip signal_voltage_switch()
> mmc: sdhci-uhs2: add set_timeout()
> mmc: sdhci-uhs2: add set_ios()
> mmc: sdhci-uhs2: add detect_init() to detect the interface
> mmc: sdhci-uhs2: add clock operations
> mmc: sdhci-uhs2: add uhs2_control() to initialise the interface
> mmc: sdhci-uhs2: add request() and others
> mmc: sdhci-uhs2: add irq() and others
> mmc: sdhci-uhs2: add add_host() and others to set up the driver
> mmc: sdhci-uhs2: add pre-detect_init hook
> mmc: sdhci-pci-gli: enable UHS-II mode for GL9755
>
> drivers/mmc/core/Makefile | 2 +-
> drivers/mmc/core/block.c | 6 +-
> drivers/mmc/core/bus.c | 37 +-
> drivers/mmc/core/core.c | 37 +-
> drivers/mmc/core/core.h | 1 +
> drivers/mmc/core/host.h | 4 +
> drivers/mmc/core/mmc_ops.c | 25 +-
> drivers/mmc/core/mmc_ops.h | 1 +
> drivers/mmc/core/regulator.c | 34 +
> drivers/mmc/core/sd.c | 11 +-
> drivers/mmc/core/sd.h | 3 +
> drivers/mmc/core/sd_ops.c | 13 +
> drivers/mmc/core/sd_ops.h | 3 +
> drivers/mmc/core/sd_uhs2.c | 1400 ++++++++++++++++++++++++++++
> drivers/mmc/host/Kconfig | 10 +
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/sdhci-pci-core.c | 16 +-
> drivers/mmc/host/sdhci-pci-gli.c | 309 ++++++-
> drivers/mmc/host/sdhci-pci.h | 3 +
> drivers/mmc/host/sdhci-uhs2.c | 1414 +++++++++++++++++++++++++++++
> drivers/mmc/host/sdhci-uhs2.h | 188 ++++
> drivers/mmc/host/sdhci.c | 343 ++++---
> drivers/mmc/host/sdhci.h | 100 +-
> include/linux/mmc/card.h | 47 +
> include/linux/mmc/core.h | 13 +
> include/linux/mmc/host.h | 91 ++
> include/linux/mmc/sd_uhs2.h | 263 ++++++
> 27 files changed, 4205 insertions(+), 170 deletions(-)
> create mode 100644 drivers/mmc/core/sd_uhs2.c
> create mode 100644 drivers/mmc/host/sdhci-uhs2.c
> create mode 100644 drivers/mmc/host/sdhci-uhs2.h
> create mode 100644 include/linux/mmc/sd_uhs2.h
>
> --
> 2.25.1
>
On Tue, 3 Jan 2023 at 12:04, Victor Shih <[email protected]> wrote:
>
> Hi, Ulf and Adrian
>
> Excuse me for disturbing your nice vacation, if you already have a
> closer look at
> a paragraph at the series after you finish the vacation, could you let
> me know your comments first, let me check it first.
I am catching up on my backlog, so I am soon ready to have a look at
this. Thanks for posting the new version!
Although, please note that my bandwidth is a bit limited this and next week too.
Kind regards
Uffe
>
> Thanks, Victor Shih
>
> On Tue, Dec 13, 2022 at 5:01 PM Victor Shih <[email protected]> wrote:
> >
> > Summary
> > =======
> > These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
> >
> > About UHS-II, roughly deal with the following three parts:
> > 1) A UHS-II detection and initialization:
> > - Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
> > [2]).
> > - Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
> > - In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
> > 3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
> > Setup Sequence.
> >
> > 2) Send Legacy SD command through SD-TRAN
> > - Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
> > compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
> > Types and Format Overview[3]).
> > - Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
> > CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
> >
> > 3) UHS-II Interrupt
> > - Except for UHS-II error interrupts, most interrupts share the original
> > interrupt registers.
> >
> > Patch structure
> > ===============
> > patch#1-#6: for core
> > patch#7-#23: for sdhci
> > patch#24: for GL9755
> >
> > Changes in v6 (Dec. 12, 2022)
> > * rebased to the linux-kernel-v6.1.0-rc8 in Ulf Hansson next branch.
> > * according to the guidance and overall architecture provided
> > by Ulf Hansson, Ben Chuang and Jason Lai to implement the
> > UHS-2 Core function based on the patches of the [V4,0/6]
> > Preparations to support SD UHS-II cards[5].
> > * according to the guidance and comments provided by
> > Adrian Hunter, Ben Chuang and AKASHI Takahiro to implement
> > the UHS-2 Host function based on the patches of the
> > [RFC,v3.1,00/27] Add support UHS-II for GL9755[4].
> > * implement the necessary function to let the UHS-2 Core/Host
> > work properly.
> > * fix most of checkpatch warnings/errors.
> > * according to the guidance and comments provided by
> > Adrian Hunter, Ben Chuang to implement the UHS-2
> > Host function based on the patches of the
> > [V5,00/26] Add support UHS-II for GL9755[6].
> > * The uhs2_post_attach_sd() has implemented in Patch#6 and
> > Patch#17 so drop the V5 version of the Patch#23.
> > * Modifies the usage of the flags used by the sdhci host for
> > MMC_UHS2_INITIALIZED.
> > * Patch#5: Drop unused definitions and functions.
> > * Patch#7: Rename definitions.
> > Use BIT() GENMASK() in some cases.
> > * Patch#8: Merge V5 version of Patch[7] and Patch[9] into
> > V6 version of Patch[8].
> > * Patch#9: Drop unnecessary function.
> > Rename used definitions.
> > * Patch#10: Drop unnecessary function and simplify some code.
> > * Patch#11: Drop unnecessary function.
> > Add new mmc_opt_regulator_set_ocr function.
> > * Patch#13: Drop unnecessary function.
> > Use GENMASK() and FIELD_PREP() in some cases.
> > * Patch#14: Drop unnecessary function.
> > Modify return value in some function.
> > Use GENMASK() and FIELD_PREP() in some cases.
> > * Patch#15: Drop unnecessary function.
> > Rename used definitions.
> > Use GENMASK() and FIELD_GET() in some cases.
> > Wrap at 100 columns in some functions.
> > * Patch#16: Drop unnecessary function.
> > * Patch#17: Drop unnecessary function.
> > Drop the unnecessary parameter when call the DBG()
> > function.
> > Rename used definitions.
> > Cancel the export state of some functions.
> > Use GENMASK() and FIELD_PREP() in some cases.
> > * Patch#18: Drop unnecessary function.
> > Add uhs2_dev_cmd function to simplify some functions.
> > Rename used definitions.
> > Cancel the export state of some functions.
> > Use GENMASK() and FIELD_PREP() in some cases.
> > * Patch#19: Drop unnecessary function.
> > Add sdhci_uhs2_mode() in some functions.
> > Rename used definitions.
> > Cancel the export state of some functions.
> > * Patch#20: Add new complete_work_fn/thread_irq_fn variables in
> > struct sdhci_host.
> > Use complete_work_fn/thread_irq_fn variables in
> > sdhci_alloc_host()/sdhci_uhs2_add_host().
> > Rename used definitions.
> > * Patch[24]: Rename used definitions.
> >
> > Reference
> > =========
> > [1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
> > [2] SD Host Controller Simplified Specification 4.20
> > [3] UHS-II Simplified Addendum 1.02
> > [4] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> > [5] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> > [6] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> >
> > ----------------- original cover letter from v5 -----------------
> > Summary
> > =======
> > These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
> >
> > About UHS-II, roughly deal with the following three parts:
> > 1) A UHS-II detection and initialization:
> > - Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
> > [2]).
> > - Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
> > - In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
> > 3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
> > Setup Sequence.
> >
> > 2) Send Legacy SD command through SD-TRAN
> > - Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
> > compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
> > Types and Format Overview[3]).
> > - Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
> > CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
> >
> > 3) UHS-II Interrupt
> > - Except for UHS-II error interrupts, most interrupts share the original
> > interrupt registers.
> >
> > Patch structure
> > ===============
> > patch#1-#6: for core
> > patch#7-#25: for sdhci
> > patch#26: for GL9755
> >
> > Changes in v5 (Oct. 19, 2022)
> > * rebased to the linux-kernel-v6.1-rc1 in Ulf Hansson next branch.
> > * according to the guidance and overall architecture provided
> > by Ulf Hansson, Ben Chuang and Jason Lai to implement the
> > UHS-2 Core function based on the patches of the [V4,0/6]
> > Preparations to support SD UHS-II cards[5].
> > * according to the guidance and comments provided by
> > Adrian Hunter, Ben Chuang and AKASHI Takahiro to implement
> > the UHS-2 Host function based on the patches of the
> > [RFC,v3.1,00/27] Add support UHS-II for GL9755[4].
> > * implement the necessary function to let the UHS-2 Core/Host
> > work properly.
> > * fix most of checkpatch warnings/errors
> >
> > Reference
> > =========
> > [1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
> > [2] SD Host Controller Simplified Specification 4.20
> > [3] UHS-II Simplified Addendum 1.02
> > [4] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> > [5] https://patchwork.kernel.org/project/linux-mmc/cover/[email protected]/
> >
> > ----------------- original cover letter from v3.1 -----------------
> > This is an interim snapshot of our next version, v4, for enabling
> > UHS-II on MMC/SD.
> >
> > It is focused on 'sdhci' side to address Adrian's comments regarding
> > "modularising" sdhci-uhs2.c.
> > The whole aim of this version is to get early feedback from Adrian (and
> > others) on this issue. Without any consensus about the code structure,
> > it would make little sense to go further ahead on sdhci side.
> > (Actually, Adrian has made no comments other than "modularising" so far.)
> >
> > I heavily reworked/refactored sdhci-uhs2.c and re-organised the patch
> > set to meet what I believe Adrian expects; no UHS-II related code in
> > Legacy (UHS-I) code or sdhci.c.
> >
> > Nevertheless, almost of all changes I made are trivial and straightforward
> > in this direction, and I believe that there is no logic changed since v3
> > except sdhci_uhs2_irq(), as ops->irq hook, where we must deal with UHS-II
> > command sequences in addition to UHS-II errors. So I added extra handlings.
> >
> > I admit that there is plenty of room for improvements (for example,
> > handling host->flags), but again the focal point here is how sdhci-uhs2.c
> > should be built as a module.
> >
> > Please review this series (particularly Patch#8-#26 and #27) from this
> > viewpoint in the first place.
> > (Ben is working on 'host' side but there is no change on 'host' side
> > in this submission except a minor tweak.)
> >
> > Thanks,
> > -Takahiro Akashi
> >
> > ------ original cover letter from v3 ------
> > Summary
> > =======
> > These patches[1] support UHS-II and fix GL9755 UHS-II compatibility.
> >
> > About UHS-II, roughly deal with the following three parts:
> > 1) A UHS-II detection and initialization:
> > - Host setup to support UHS-II (Section 3.13.1 Host Controller Setup Sequence
> > [2]).
> > - Detect a UHS-II I/F (Section 3.13.2 Card Interface Detection Sequence[2]).
> > - In step(9) of Section 3.13.2 in [2], UHS-II initialization is include Section
> > 3.13.3 UHS-II Card Initialization and Section 3.13.4 UHS-II Setting Register
> > Setup Sequence.
> >
> > 2) Send Legacy SD command through SD-TRAN
> > - Encapsulated SD packets are defined in SD-TRAN in order to ensure Legacy SD
> > compatibility and preserve Legacy SD infrastructures (Section 7.1.1 Packet
> > Types and Format Overview[3]).
> > - Host issue a UHS-II CCMD packet or a UHS-II DCMD (Section 3.13.5 UHS-II
> > CCMD Packet issuing and Section 3.13.6 UHS-II DCMD Packet issuing[2]).
> >
> > 3) UHS-II Interrupt
> > - Except for UHS-II error interrupts, most interrupts share the original
> > interrupt registers.
> >
> > Patch structure
> > ===============
> > patch#1-#7: for core
> > patch#8-#17: for sdhci
> > patch#18-#21: for GL9755
> >
> > Tests
> > =====
> > Ran 'dd' command to evaluate the performance:
> > (SanDisk UHS-II card on GL9755 controller)
> > Read Write
> > UHS-II disabled (UHS-I): 88.3MB/s 60.7MB/s
> > UHS-II enabled : 206MB/s 80MB/s
> >
> > TODO
> > ====
> > - replace some define with BIT macro
> >
> > Reference
> > =========
> > [1] https://gitlab.com/ben.chuang/linux-uhs2-gl9755.git
> > [2] SD Host Controller Simplified Specification 4.20
> > [3] UHS-II Simplified Addendum 1.02
> >
> > Changes in v3 (Jul. 10, 2020)
> > * rebased to v5.8-rc4
> > * add copyright notice
> > * reorganize the patch set and split some commits into smaller ones
> > * separate uhs-2 headers from others
> > * correct wrong spellings
> > * fix most of checkpatch warnings/errors
> > * remove all k[cz]alloc() from the code
> > * guard sdhci-uhs2 specific code with
> > 'if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2))'
> > * make sdhci-uhs2.c as a module
> > * trivial changes, including
> > - rename back sdhci-core.c to sdhci.c
> > - allow vendor code to disable uhs2 if v4_mode == 0
> > in __sdhci_add_host()
> > - merge uhs2_power_up() into mmc_power_up()
> > - remove flag_uhs2 from mmc_attach_sd()
> > - add function descriptions to EXPORT'ed functions
> > - other minor code optimization
> >
> > Changes in v2 (Jan. 9, 2020)
> > * rebased to v5.5-rc5
> >
> > AKASHI Takahiro (1):
> > mmc: sdhci-pci: add UHS-II support framework
> >
> > Ben Chuang (1):
> > mmc: sdhci-uhs2: add post-mmc_attach_sd hook
> >
> > Ulf Hansson (3):
> > mmc: core: Prepare to support SD UHS-II cards
> > mmc: core: Announce successful insertion of an SD UHS-II card
> > mmc: core: Extend support for mmc regulators with a vqmmc2
> >
> > Victor Shih (19):
> > mmc: core: Cleanup printing of speed mode at card insertion
> > mmc: core: Add definitions for SD UHS-II cards
> > mmc: core: Support UHS-II card control and access
> > mmc: sdhci: add UHS-II related definitions in headers
> > mmc: sdhci: add UHS-II module and add a kernel configuration
> > mmc: sdhci-uhs2: dump UHS-II registers
> > mmc: sdhci-uhs2: add reset function and uhs2_mode function
> > mmc: sdhci-uhs2: add set_power() to support vdd2
> > mmc: sdhci-uhs2: skip signal_voltage_switch()
> > mmc: sdhci-uhs2: add set_timeout()
> > mmc: sdhci-uhs2: add set_ios()
> > mmc: sdhci-uhs2: add detect_init() to detect the interface
> > mmc: sdhci-uhs2: add clock operations
> > mmc: sdhci-uhs2: add uhs2_control() to initialise the interface
> > mmc: sdhci-uhs2: add request() and others
> > mmc: sdhci-uhs2: add irq() and others
> > mmc: sdhci-uhs2: add add_host() and others to set up the driver
> > mmc: sdhci-uhs2: add pre-detect_init hook
> > mmc: sdhci-pci-gli: enable UHS-II mode for GL9755
> >
> > drivers/mmc/core/Makefile | 2 +-
> > drivers/mmc/core/block.c | 6 +-
> > drivers/mmc/core/bus.c | 37 +-
> > drivers/mmc/core/core.c | 37 +-
> > drivers/mmc/core/core.h | 1 +
> > drivers/mmc/core/host.h | 4 +
> > drivers/mmc/core/mmc_ops.c | 25 +-
> > drivers/mmc/core/mmc_ops.h | 1 +
> > drivers/mmc/core/regulator.c | 34 +
> > drivers/mmc/core/sd.c | 11 +-
> > drivers/mmc/core/sd.h | 3 +
> > drivers/mmc/core/sd_ops.c | 13 +
> > drivers/mmc/core/sd_ops.h | 3 +
> > drivers/mmc/core/sd_uhs2.c | 1400 ++++++++++++++++++++++++++++
> > drivers/mmc/host/Kconfig | 10 +
> > drivers/mmc/host/Makefile | 1 +
> > drivers/mmc/host/sdhci-pci-core.c | 16 +-
> > drivers/mmc/host/sdhci-pci-gli.c | 309 ++++++-
> > drivers/mmc/host/sdhci-pci.h | 3 +
> > drivers/mmc/host/sdhci-uhs2.c | 1414 +++++++++++++++++++++++++++++
> > drivers/mmc/host/sdhci-uhs2.h | 188 ++++
> > drivers/mmc/host/sdhci.c | 343 ++++---
> > drivers/mmc/host/sdhci.h | 100 +-
> > include/linux/mmc/card.h | 47 +
> > include/linux/mmc/core.h | 13 +
> > include/linux/mmc/host.h | 91 ++
> > include/linux/mmc/sd_uhs2.h | 263 ++++++
> > 27 files changed, 4205 insertions(+), 170 deletions(-)
> > create mode 100644 drivers/mmc/core/sd_uhs2.c
> > create mode 100644 drivers/mmc/host/sdhci-uhs2.c
> > create mode 100644 drivers/mmc/host/sdhci-uhs2.h
> > create mode 100644 include/linux/mmc/sd_uhs2.h
> >
> > --
> > 2.25.1
> >
On 13/12/22 11:00, Victor Shih wrote:
> Embed UHS-II access/control functionality into the MMC request
> processing flow.
>
> Signed-off-by: Ulf Hansson <[email protected]>
> Signed-off-by: Jason Lai <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/core/block.c | 6 +-
> drivers/mmc/core/core.c | 20 +
> drivers/mmc/core/mmc_ops.c | 25 +-
> drivers/mmc/core/mmc_ops.h | 1 +
> drivers/mmc/core/sd.c | 11 +-
> drivers/mmc/core/sd.h | 3 +
> drivers/mmc/core/sd_ops.c | 13 +
> drivers/mmc/core/sd_ops.h | 3 +
> drivers/mmc/core/sd_uhs2.c | 1171 +++++++++++++++++++++++++++++++++++-
> 9 files changed, 1206 insertions(+), 47 deletions(-)
>
<SNIP>
> diff --git a/drivers/mmc/core/sd_uhs2.c b/drivers/mmc/core/sd_uhs2.c
> index 800957f74632..a79eb08ec540 100644
> --- a/drivers/mmc/core/sd_uhs2.c
> +++ b/drivers/mmc/core/sd_uhs2.c
<SNIP>
> +/*
> + * Mask off any voltages we don't support and select
> + * the lowest voltage
> + */
> +u32 sd_uhs2_select_voltage(struct mmc_host *host, u32 ocr)
> +{
> + int bit;
> + int err;
> +
> + /*
> + * Sanity check the voltages that the card claims to
> + * support.
> + */
> + if (ocr & 0x7F) {
> + dev_warn(mmc_dev(host), "card claims to support voltages below defined range\n");
> + ocr &= ~0x7F;
> + }
> +
> + ocr &= host->ocr_avail;
> + if (!ocr) {
> + dev_warn(mmc_dev(host), "no support for card's volts\n");
> + return 0;
> + }
> +
> + if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) {
> + bit = ffs(ocr) - 1;
> + ocr &= 3 << bit;
> + /* Power cycle */
> + err = sd_uhs2_power_off(host);
> + if (err)
> + return 0;
> + err = sd_uhs2_reinit(host);
This looks circular:
sd_uhs2_select_voltage
-> sd_uhs2_reinit
-> sd_uhs2_init_card
-> sd_uhs2_legacy_init
-> sd_uhs2_select_voltage
> + if (err)
> + return 0;
> + } else {
> + bit = fls(ocr) - 1;
> + ocr &= 3 << bit;
> + if (bit != host->ios.vdd)
> + dev_warn(mmc_dev(host), "exceeding card's volts\n");
> + }
> +
> + return ocr;
> +}
> +
<SNIP>
On 13/12/22 11:00, Victor Shih wrote:
> This is a sdhci version of mmc's uhs2_set_reg operation.
> UHS-II interface (related registers) will be initialised here.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/sdhci-uhs2.c | 92 +++++++++++++++++++++++++++++++++++
> drivers/mmc/host/sdhci.c | 11 +++++
> 2 files changed, 103 insertions(+)
>
> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> index c7e411fa0a6e..de15a6559702 100644
> --- a/drivers/mmc/host/sdhci-uhs2.c
> +++ b/drivers/mmc/host/sdhci-uhs2.c
> @@ -276,6 +276,48 @@ static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> sdhci_set_clock(host, host->clock);
> }
>
> +static void sdhci_uhs2_set_config(struct sdhci_host *host)
> +{
> + u32 value;
> + u16 sdhci_uhs2_set_ptr = sdhci_readw(host, SDHCI_UHS2_SETTINGS_PTR);
> + u16 sdhci_uhs2_gen_set_reg = (sdhci_uhs2_set_ptr + 0);
> + u16 sdhci_uhs2_phy_set_reg = (sdhci_uhs2_set_ptr + 4);
> + u16 sdhci_uhs2_tran_set_reg = (sdhci_uhs2_set_ptr + 8);
> + u16 sdhci_uhs2_tran_set_1_reg = (sdhci_uhs2_set_ptr + 12);
> +
> + /* Set Gen Settings */
> + value = FIELD_PREP(SDHCI_UHS2_GEN_SETTINGS_N_LANES_MASK, host->mmc->uhs2_caps.n_lanes_set);
> + sdhci_writel(host, value, sdhci_uhs2_gen_set_reg);
> +
> + /* Set PHY Settings */
> + value = FIELD_PREP(SDHCI_UHS2_PHY_N_LSS_DIR_MASK, host->mmc->uhs2_caps.n_lss_dir_set) |
> + FIELD_PREP(SDHCI_UHS2_PHY_N_LSS_SYN_MASK, host->mmc->uhs2_caps.n_lss_sync_set);
> + if (host->mmc->flags & MMC_UHS2_SPEED_B)
> + value |= SDHCI_UHS2_PHY_SET_SPEED_B;
> + sdhci_writel(host, value, sdhci_uhs2_phy_set_reg);
> +
> + /* Set LINK-TRAN Settings */
> + value = FIELD_PREP(SDHCI_UHS2_TRAN_RETRY_CNT_MASK, host->mmc->uhs2_caps.max_retry_set) |
> + FIELD_PREP(SDHCI_UHS2_TRAN_N_FCU_MASK, host->mmc->uhs2_caps.n_fcu_set);
> + sdhci_writel(host, value, sdhci_uhs2_tran_set_reg);
> + sdhci_writel(host, host->mmc->uhs2_caps.n_data_gap_set, sdhci_uhs2_tran_set_1_reg);
> +}
> +
> +static int sdhci_uhs2_check_dormant(struct sdhci_host *host)
> +{
> + u32 val;
> + /* 100ms */
> + int timeout = 100000;
> +
> + if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_IN_DORMANT_STATE),
> + 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
> + pr_warn("%s: UHS2 IN_DORMANT fail in 100ms.\n", mmc_hostname(host->mmc));
> + sdhci_dumpregs(host);
> + return -EIO;
> + }
> + return 0;
> +}
> +
> /*****************************************************************************\
> * *
> * MMC callbacks *
> @@ -361,6 +403,55 @@ static int sdhci_uhs2_enable_clk(struct mmc_host *mmc)
> return 0;
> }
>
> +static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc);
> +
> +static int sdhci_uhs2_control(struct mmc_host *mmc, enum sd_uhs2_operation op)
> +{
> + struct sdhci_host *host = mmc_priv(mmc);
> + int err = 0;
> + u16 sdhci_uhs2_set_ptr = sdhci_readw(host, SDHCI_UHS2_SETTINGS_PTR);
> + u16 sdhci_uhs2_phy_set_reg = (sdhci_uhs2_set_ptr + 4);
> +
> + DBG("Begin uhs2 control, act %d.\n", op);
> +
> + switch (op) {
> + case UHS2_PHY_INIT:
> + err = sdhci_uhs2_do_detect_init(mmc);
> + break;
> + case UHS2_SET_CONFIG:
> + sdhci_uhs2_set_config(host);
> + break;
> + case UHS2_ENABLE_INT:
> + sdhci_uhs2_clear_set_irqs(host, 0, SDHCI_INT_CARD_INT);
> + break;
> + case UHS2_DISABLE_INT:
> + sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_CARD_INT, 0);
> + break;
> + case UHS2_SET_SPEED_B:
> + sdhci_writeb(host, SDHCI_UHS2_PHY_SET_SPEED_B, sdhci_uhs2_phy_set_reg);
> + break;
> + case UHS2_CHECK_DORMANT:
> + err = sdhci_uhs2_check_dormant(host);
> + break;
> + case UHS2_DISABLE_CLK:
> + err = sdhci_uhs2_disable_clk(mmc);
> + break;
> + case UHS2_ENABLE_CLK:
> + err = sdhci_uhs2_enable_clk(mmc);
> + break;
> + case UHS2_POST_ATTACH_SD:
> + host->ops->uhs2_post_attach_sd(host);
> + break;
> + default:
> + pr_err("%s: input sd uhs2 operation %d is wrong!\n",
> + mmc_hostname(host->mmc), op);
> + err = -EIO;
> + break;
> + }
> +
> + return err;
> +}
> +
> /*****************************************************************************\
> * *
> * Driver init/exit *
> @@ -489,6 +580,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> host->mmc_host_ops.start_signal_voltage_switch =
> sdhci_uhs2_start_signal_voltage_switch;
> host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
> + host->mmc_host_ops.uhs2_control = sdhci_uhs2_control;
>
> return 0;
> }
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 49bbdc155b2b..3656e66fcca7 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -234,6 +234,17 @@ void sdhci_reset(struct sdhci_host *host, u8 mask)
> }
> EXPORT_SYMBOL_GPL(sdhci_reset);
>
> +void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
> +{
> + u32 ier;
> +
> + ier = sdhci_readl(host, SDHCI_INT_ENABLE);
> + ier &= ~clear;
> + ier |= set;
> + sdhci_writel(host, ier, SDHCI_INT_ENABLE);
> + sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
> +}
Not used anymore
> +
> static bool sdhci_do_reset(struct sdhci_host *host, u8 mask)
> {
> if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
On 13/12/22 11:00, Victor Shih wrote:
> Sdhci_uhs2_do_detect_init() is a sdhci version of mmc's uhs2_detect_init
> operation. After detected, the host's UHS-II capabilities will be set up
> here and interrupts will also be enabled.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/sdhci-uhs2.c | 117 ++++++++++++++++++++++++++++++++++
> 1 file changed, 117 insertions(+)
>
> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> index 3d52d35a91a5..52587025d5e3 100644
> --- a/drivers/mmc/host/sdhci-uhs2.c
> +++ b/drivers/mmc/host/sdhci-uhs2.c
> @@ -335,6 +335,123 @@ int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> * *
> \*****************************************************************************/
>
> +static int sdhci_uhs2_interface_detect(struct sdhci_host *host)
> +{
> + /* 100ms */
> + int timeout = 100000;
> + u32 val;
> +
> + udelay(200); /* wait for 200us before check */
> +
> + if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_IF_DETECT),
> + 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
> + pr_warn("%s: not detect UHS2 interface in 200us.\n", mmc_hostname(host->mmc));
> + sdhci_dumpregs(host);
> + return -EIO;
> + }
> +
> + /* Enable UHS2 error interrupts */
> + sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
> +
> + /* 150ms */
> + timeout = 150000;
> + if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_LANE_SYNC),
> + 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
> + pr_warn("%s: UHS2 Lane sync fail in 150ms.\n", mmc_hostname(host->mmc));
> + sdhci_dumpregs(host);
> + return -EIO;
> + }
> +
> + DBG("%s: UHS2 Lane synchronized in UHS2 mode, PHY is initialized.\n",
> + mmc_hostname(host->mmc));
> + return 0;
> +}
> +
> +static int sdhci_uhs2_init(struct sdhci_host *host)
> +{
> + u16 caps_ptr = 0;
> + u32 caps_gen = 0;
> + u32 caps_phy = 0;
> + u32 caps_tran[2] = {0, 0};
> + struct mmc_host *mmc = host->mmc;
> +
> + caps_ptr = sdhci_readw(host, SDHCI_UHS2_CAPS_PTR);
> + if (caps_ptr < 0x100 || caps_ptr > 0x1FF) {
> + pr_err("%s: SDHCI_UHS2_CAPS_PTR(%d) is wrong.\n",
> + mmc_hostname(mmc), caps_ptr);
> + return -ENODEV;
> + }
> + caps_gen = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_OFFSET);
> + caps_phy = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_PHY_OFFSET);
> + caps_tran[0] = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_TRAN_OFFSET);
> + caps_tran[1] = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_TRAN_1_OFFSET);
> +
> + /* General Caps */
> + mmc->uhs2_caps.dap = caps_gen & SDHCI_UHS2_CAPS_DAP_MASK;
> + mmc->uhs2_caps.gap = FIELD_GET(SDHCI_UHS2_CAPS_GAP_MASK, caps_gen);
> + mmc->uhs2_caps.n_lanes = FIELD_GET(SDHCI_UHS2_CAPS_LANE_MASK, caps_gen);
> + mmc->uhs2_caps.addr64 = (caps_gen & SDHCI_UHS2_CAPS_ADDR_64) ? 1 : 0;
> + mmc->uhs2_caps.card_type = FIELD_GET(SDHCI_UHS2_CAPS_DEV_TYPE_MASK, caps_gen);
> +
> + /* PHY Caps */
> + mmc->uhs2_caps.phy_rev = caps_phy & SDHCI_UHS2_CAPS_PHY_REV_MASK;
> + mmc->uhs2_caps.speed_range = FIELD_GET(SDHCI_UHS2_CAPS_PHY_RANGE_MASK, caps_phy);
> + mmc->uhs2_caps.n_lss_sync = FIELD_GET(SDHCI_UHS2_CAPS_PHY_N_LSS_SYN_MASK, caps_phy);
> + mmc->uhs2_caps.n_lss_dir = FIELD_GET(SDHCI_UHS2_CAPS_PHY_N_LSS_DIR_MASK, caps_phy);
> + if (mmc->uhs2_caps.n_lss_sync == 0)
> + mmc->uhs2_caps.n_lss_sync = 16 << 2;
> + else
> + mmc->uhs2_caps.n_lss_sync <<= 2;
> + if (mmc->uhs2_caps.n_lss_dir == 0)
> + mmc->uhs2_caps.n_lss_dir = 16 << 3;
> + else
> + mmc->uhs2_caps.n_lss_dir <<= 3;
> +
> + /* LINK/TRAN Caps */
> + mmc->uhs2_caps.link_rev = caps_tran[0] & SDHCI_UHS2_CAPS_TRAN_LINK_REV_MASK;
> + mmc->uhs2_caps.n_fcu = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_N_FCU_MASK, caps_tran[0]);
> + if (mmc->uhs2_caps.n_fcu == 0)
> + mmc->uhs2_caps.n_fcu = 256;
> + mmc->uhs2_caps.host_type = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_HOST_TYPE_MASK, caps_tran[0]);
> + mmc->uhs2_caps.maxblk_len = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_BLK_LEN_MASK, caps_tran[0]);
> + mmc->uhs2_caps.n_data_gap = caps_tran[1] & SDHCI_UHS2_CAPS_TRAN_1_N_DATA_GAP_MASK;
> +
> + return 0;
> +}
> +
> +static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc)
> +{
> + struct sdhci_host *host = mmc_priv(mmc);
> + int ret = -EIO;
> +
> + DBG("Begin do uhs2 detect init.\n");
> +
> + if (sdhci_uhs2_interface_detect(host)) {
> + pr_warn("%s: cannot detect UHS2 interface.\n",
> + mmc_hostname(host->mmc));
> + goto out;
> + }
> +
> + if (sdhci_uhs2_init(host)) {
> + pr_warn("%s: UHS2 init fail.\n", mmc_hostname(host->mmc));
> + goto out;
> + }
> +
> + /* Init complete, do soft reset and enable UHS2 error irqs. */
> + host->ops->uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
host->ops->uhs2_reset -> sdhci_uhs2_reset
> + sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
> + /*
> + * N.B SDHCI_INT_ENABLE and SDHCI_SIGNAL_ENABLE was cleared
> + * by SDHCI_UHS2_SW_RESET_SD
> + */
> + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
> + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
> +
> + ret = 0;
> +out:
> + return ret;
> +}
> +
> static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> {
> host->mmc_host_ops.start_signal_voltage_switch =
On 13/12/22 11:00, Victor Shih wrote:
> This is a sdhci version of mmc's set_ios operation.
> It covers both UHS-I and UHS-II.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/sdhci-uhs2.c | 96 +++++++++++++++++++++++++++++++++++
> drivers/mmc/host/sdhci-uhs2.h | 1 +
> drivers/mmc/host/sdhci.c | 53 +++++++++++--------
> drivers/mmc/host/sdhci.h | 2 +
> 4 files changed, 131 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> index 00b1b69b49ea..3d52d35a91a5 100644
> --- a/drivers/mmc/host/sdhci-uhs2.c
> +++ b/drivers/mmc/host/sdhci-uhs2.c
> @@ -213,6 +213,68 @@ void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
> }
> EXPORT_SYMBOL_GPL(sdhci_uhs2_set_timeout);
>
> +/**
> + * sdhci_uhs2_clear_set_irqs - set Error Interrupt Status Enable register
> + * @host: SDHCI host
> + * @clear: bit-wise clear mask
> + * @set: bit-wise set mask
> + *
> + * Set/unset bits in UHS-II Error Interrupt Status Enable register
> + */
> +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
> +{
> + u32 ier;
> +
> + ier = sdhci_readl(host, SDHCI_UHS2_INT_STATUS_ENABLE);
> + ier &= ~clear;
> + ier |= set;
> + sdhci_writel(host, ier, SDHCI_UHS2_INT_STATUS_ENABLE);
> + sdhci_writel(host, ier, SDHCI_UHS2_INT_SIGNAL_ENABLE);
> +}
> +EXPORT_SYMBOL_GPL(sdhci_uhs2_clear_set_irqs);
> +
> +static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> + struct sdhci_host *host = mmc_priv(mmc);
> + u8 cmd_res, dead_lock;
> + u16 ctrl_2;
> +
> + /* UHS2 Timeout Control */
> + sdhci_calc_timeout_uhs2(host, &cmd_res, &dead_lock);
> +
> + /* change to use calculate value */
> + cmd_res |= FIELD_PREP(SDHCI_UHS2_TIMER_CTRL_DEADLOCK_MASK, dead_lock);
> +
> + sdhci_uhs2_clear_set_irqs(host,
> + SDHCI_UHS2_INT_CMD_TIMEOUT |
> + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT,
> + 0);
> + sdhci_writeb(host, cmd_res, SDHCI_UHS2_TIMER_CTRL);
> + sdhci_uhs2_clear_set_irqs(host, 0,
> + SDHCI_UHS2_INT_CMD_TIMEOUT |
> + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT);
> +
> + /* UHS2 timing */
Please extend comment to include:
Note, UHS2 timing is disabled when powering off
> + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> + if (ios->timing == MMC_TIMING_SD_UHS2)
> + ctrl_2 |= SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE;
> + else
> + ctrl_2 &= ~(SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE);
> + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +
> + if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
> + sdhci_enable_preset_value(host, true);
> +
> + if (host->ops->set_power)
> + host->ops->set_power(host, ios->power_mode, ios->vdd);
> + else
> + sdhci_uhs2_set_power(host, ios->power_mode, ios->vdd);
sdhci_set_ios_common() already set the power. Are both needed?
> + udelay(100);
Please add a comment for why this delay is here.
> +
> + host->timing = ios->timing;
Please move this up to where the timing change is.
> + sdhci_set_clock(host, host->clock);
> +}
> +
> /*****************************************************************************\
> * *
> * MMC callbacks *
> @@ -234,6 +296,39 @@ static int sdhci_uhs2_start_signal_voltage_switch(struct mmc_host *mmc,
> return sdhci_start_signal_voltage_switch(mmc, ios);
> }
>
> +int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
Should be static
> +{
> + struct sdhci_host *host = mmc_priv(mmc);
> +
> + if (!(host->version >= SDHCI_SPEC_400) ||
> + !(host->mmc->flags & MMC_UHS2_SUPPORT &&
> + host->mmc->caps2 & MMC_CAP2_SD_UHS2)) {
This can be just:
if (!sdhci_uhs2_mode(host)) {
Not sure if this is actually possible?
> + sdhci_set_ios(mmc, ios);
> + return 0;
> + }
> +
> + if (ios->power_mode == MMC_POWER_UNDEFINED)
> + return 0;
> +
> + if (host->flags & SDHCI_DEVICE_DEAD) {
> + if (!IS_ERR(mmc->supply.vmmc) &&
> + ios->power_mode == MMC_POWER_OFF)
> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> + if (!IS_ERR_OR_NULL(mmc->supply.vmmc2) &&
> + ios->power_mode == MMC_POWER_OFF)
> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
This can be just:
if (ios->power_mode == MMC_POWER_OFF) {
mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
}
> + return -1;
> + }
> +
> + host->timing = ios->timing;
__sdhci_uhs2_set_ios() does this so it is not needed here.
> +
> + sdhci_set_ios_common(mmc, ios);
> +
> + __sdhci_uhs2_set_ios(mmc, ios);
> +
> + return 0;
> +}
> +
> /*****************************************************************************\
> * *
> * Driver init/exit *
> @@ -244,6 +339,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> {
> host->mmc_host_ops.start_signal_voltage_switch =
> sdhci_uhs2_start_signal_voltage_switch;
> + host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
>
> return 0;
> }
> diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
> index a58ef19c08aa..184fee80253c 100644
> --- a/drivers/mmc/host/sdhci-uhs2.h
> +++ b/drivers/mmc/host/sdhci-uhs2.h
> @@ -178,5 +178,6 @@ void sdhci_uhs2_dump_regs(struct sdhci_host *host);
> bool sdhci_uhs2_mode(struct sdhci_host *host);
> void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
> void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
> +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
>
> #endif /* __SDHCI_UHS2_H */
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 99633a3ef549..49bbdc155b2b 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -47,8 +47,6 @@
> static unsigned int debug_quirks = 0;
> static unsigned int debug_quirks2;
>
> -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
> -
> static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
>
> void sdhci_dumpregs(struct sdhci_host *host)
> @@ -1877,6 +1875,9 @@ static u16 sdhci_get_preset_value(struct sdhci_host *host)
> case MMC_TIMING_MMC_HS400:
> preset = sdhci_readw(host, SDHCI_PRESET_FOR_HS400);
> break;
> + case MMC_TIMING_SD_UHS2:
> + preset = sdhci_readw(host, SDHCI_PRESET_FOR_UHS2);
> + break;
> default:
> pr_warn("%s: Invalid UHS-I mode selected\n",
> mmc_hostname(host->mmc));
> @@ -2325,24 +2326,9 @@ static bool sdhci_presetable_values_change(struct sdhci_host *host, struct mmc_i
> (sdhci_preset_needed(host, ios->timing) || host->drv_type != ios->drv_type);
> }
>
> -void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios)
> {
> struct sdhci_host *host = mmc_priv(mmc);
> - bool reinit_uhs = host->reinit_uhs;
> - bool turning_on_clk = false;
> - u8 ctrl;
> -
> - host->reinit_uhs = false;
> -
> - if (ios->power_mode == MMC_POWER_UNDEFINED)
> - return;
> -
> - if (host->flags & SDHCI_DEVICE_DEAD) {
> - if (!IS_ERR(mmc->supply.vmmc) &&
> - ios->power_mode == MMC_POWER_OFF)
> - mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> - return;
> - }
>
> /*
> * Reset the chip on each power off.
> @@ -2359,8 +2345,6 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> sdhci_enable_preset_value(host, false);
>
> if (!ios->clock || ios->clock != host->clock) {
> - turning_on_clk = ios->clock && !host->clock;
> -
> host->ops->set_clock(host, ios->clock);
> host->clock = ios->clock;
>
> @@ -2381,6 +2365,32 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> host->ops->set_power(host, ios->power_mode, ios->vdd);
> else
> sdhci_set_power(host, ios->power_mode, ios->vdd);
> +}
> +EXPORT_SYMBOL_GPL(sdhci_set_ios_common);
> +
> +void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> + struct sdhci_host *host = mmc_priv(mmc);
> + bool reinit_uhs = host->reinit_uhs;
> + bool turning_on_clk = false;
> + u8 ctrl;
> +
> + host->reinit_uhs = false;
> +
> + if (ios->power_mode == MMC_POWER_UNDEFINED)
> + return;
> +
> + if (host->flags & SDHCI_DEVICE_DEAD) {
> + if (!IS_ERR(mmc->supply.vmmc) &&
> + ios->power_mode == MMC_POWER_OFF)
> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> + return;
> + }
> +
> + sdhci_set_ios_common(mmc, ios);
> +
> + if (!ios->clock || ios->clock != host->clock)
> + turning_on_clk = ios->clock && !host->clock;
>
> if (host->ops->platform_send_init_74_clocks)
> host->ops->platform_send_init_74_clocks(host, ios->power_mode);
> @@ -2959,7 +2969,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
> }
> EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
>
> -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> {
> /* Host Controller v3.00 defines preset value registers */
> if (host->version < SDHCI_SPEC_300)
> @@ -2987,6 +2997,7 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> host->preset_enabled = enable;
> }
> }
> +EXPORT_SYMBOL_GPL(sdhci_enable_preset_value);
>
> static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
> int err)
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index df7fa0c0ebf8..c2f989dc2361 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -850,6 +850,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
> void sdhci_reset(struct sdhci_host *host, u8 mask);
> void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
> int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
> +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
> +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios);
> void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
> int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> struct mmc_ios *ios);
On 13/12/22 11:00, Victor Shih wrote:
> This is a UHS-II version of sdhci's set_power operation.
> VDD2, as well as VDD, is handled here.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/sdhci-uhs2.c | 46 +++++++++++++++++++++++++
> drivers/mmc/host/sdhci.c | 63 +++++++++++++++++++----------------
> drivers/mmc/host/sdhci.h | 1 +
> 3 files changed, 82 insertions(+), 28 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> index ae862e1eadab..780491781613 100644
> --- a/drivers/mmc/host/sdhci-uhs2.c
> +++ b/drivers/mmc/host/sdhci-uhs2.c
> @@ -57,6 +57,13 @@ EXPORT_SYMBOL_GPL(sdhci_uhs2_dump_regs);
> * *
> \*****************************************************************************/
>
> +static inline int mmc_opt_regulator_set_ocr(struct mmc_host *mmc,
> + struct regulator *supply,
> + unsigned short vdd_bit)
> +{
> + return IS_ERR_OR_NULL(supply) ? 0 : mmc_regulator_set_ocr(mmc, supply, vdd_bit);
> +}
> +
> bool sdhci_uhs2_mode(struct sdhci_host *host)
> {
> return host->mmc->flags & MMC_UHS2_SUPPORT;
> @@ -95,6 +102,45 @@ void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask)
> }
> EXPORT_SYMBOL_GPL(sdhci_uhs2_reset);
>
> +static void sdhci_uhs2_set_power(struct sdhci_host *host, unsigned char mode,
> + unsigned short vdd)
> +{
> + struct mmc_host *mmc = host->mmc;
> + u8 pwr;
> +
> + if (mode != MMC_POWER_OFF) {
> + pwr = sdhci_get_vdd_value(vdd);
> + if (!pwr)
> + WARN(1, "%s: Invalid vdd %#x\n",
> + mmc_hostname(host->mmc), vdd);
> + pwr |= SDHCI_VDD2_POWER_180;
> + }
> +
> + if (host->pwr == pwr)
> + return;
> + host->pwr = pwr;
> +
> + if (pwr == 0) {
> + sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
> +
> + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
> + } else {
> + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
> + /* support 1.8v only for now */
> + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, fls(MMC_VDD2_165_195) - 1);
> +
/* Clear the power reg before setting a new value */
sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
> + /* vdd first */
> + pwr |= SDHCI_POWER_ON;
> + sdhci_writeb(host, pwr & 0xf, SDHCI_POWER_CONTROL);
> + mdelay(5);
> +
> + pwr |= SDHCI_VDD2_POWER_ON;
> + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
> + mdelay(5);
> + }
> +}
> +
> /*****************************************************************************\
> * *
> * Driver init/exit *
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 9b66b9a32c72..99633a3ef549 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -23,7 +23,7 @@
> #include <linux/regulator/consumer.h>
> #include <linux/pm_runtime.h>
> #include <linux/of.h>
> -
> +#include <linux/bug.h>
> #include <linux/leds.h>
>
> #include <linux/mmc/mmc.h>
> @@ -2061,41 +2061,48 @@ static void sdhci_set_power_reg(struct sdhci_host *host, unsigned char mode,
> sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
> }
>
> +unsigned short sdhci_get_vdd_value(unsigned short vdd)
> +{
> + u8 pwr;
It is simpler without pwr
> +
> + switch (1 << vdd) {
> + case MMC_VDD_165_195:
> + /*
> + * Without a regulator, SDHCI does not support 2.0v
> + * so we only get here if the driver deliberately
> + * added the 2.0v range to ocr_avail. Map it to 1.8v
> + * for the purpose of turning on the power.
> + */
> + case MMC_VDD_20_21:
> + pwr = SDHCI_POWER_180;
> + break;
return SDHCI_POWER_180;
> + case MMC_VDD_29_30:
> + case MMC_VDD_30_31:
> + pwr = SDHCI_POWER_300;
> + break;
return SDHCI_POWER_300;
> + case MMC_VDD_32_33:
> + case MMC_VDD_33_34:
Missing:
/*
* 3.4 ~ 3.6V are valid only for those platforms where it's
* known that the voltage range is supported by hardware.
*/
case MMC_VDD_34_35:
case MMC_VDD_35_36:
> + pwr = SDHCI_POWER_330;
> + break;
return SDHCI_POWER_330;
> + default:
> + pwr = 0;
> + break;
return 0;
> + }
> +
> + return pwr;
Not needed. Control never reaches it anymore.
> +}
> +EXPORT_SYMBOL_GPL(sdhci_get_vdd_value);
> +
> void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
> unsigned short vdd)
> {
> u8 pwr = 0;
>
> if (mode != MMC_POWER_OFF) {
> - switch (1 << vdd) {
> - case MMC_VDD_165_195:
> - /*
> - * Without a regulator, SDHCI does not support 2.0v
> - * so we only get here if the driver deliberately
> - * added the 2.0v range to ocr_avail. Map it to 1.8v
> - * for the purpose of turning on the power.
> - */
> - case MMC_VDD_20_21:
> - pwr = SDHCI_POWER_180;
> - break;
> - case MMC_VDD_29_30:
> - case MMC_VDD_30_31:
> - pwr = SDHCI_POWER_300;
> - break;
> - case MMC_VDD_32_33:
> - case MMC_VDD_33_34:
> - /*
> - * 3.4 ~ 3.6V are valid only for those platforms where it's
> - * known that the voltage range is supported by hardware.
> - */
> - case MMC_VDD_34_35:
> - case MMC_VDD_35_36:
> - pwr = SDHCI_POWER_330;
> - break;
> - default:
> + pwr = sdhci_get_vdd_value(vdd);
> + if (!pwr) {
> WARN(1, "%s: Invalid vdd %#x\n",
> mmc_hostname(host->mmc), vdd);
> - break;
> }
> }
>
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index f41c19c76994..df7fa0c0ebf8 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -840,6 +840,7 @@ void sdhci_set_power(struct sdhci_host *host, unsigned char mode,
> void sdhci_set_power_and_bus_voltage(struct sdhci_host *host,
> unsigned char mode,
> unsigned short vdd);
> +unsigned short sdhci_get_vdd_value(unsigned short vdd);
> void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
> unsigned short vdd);
> int sdhci_get_cd_nogpio(struct mmc_host *mmc);
On 13/12/22 11:00, Victor Shih wrote:
> This is a sdhci version of mmc's request operation.
> It covers both UHS-I and UHS-II.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/sdhci-uhs2.c | 418 ++++++++++++++++++++++++++++++++++
> drivers/mmc/host/sdhci.c | 78 ++++---
> drivers/mmc/host/sdhci.h | 14 ++
> 3 files changed, 481 insertions(+), 29 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> index de15a6559702..7a34f66c4215 100644
> --- a/drivers/mmc/host/sdhci-uhs2.c
> +++ b/drivers/mmc/host/sdhci-uhs2.c
> @@ -15,6 +15,7 @@
> #include <linux/iopoll.h>
> #include <linux/bitfield.h>
> #include <linux/ktime.h>
> +#include <linux/mmc/mmc.h>
>
> #include "sdhci.h"
> #include "sdhci-uhs2.h"
> @@ -25,6 +26,8 @@
> #define SDHCI_UHS2_DUMP(f, x...) \
> pr_err("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x)
>
> +#define UHS2_ARG_IOADR_MASK 0xfff
> +
> void sdhci_uhs2_dump_regs(struct sdhci_host *host)
> {
> if (!(host->mmc->flags & MMC_UHS2_SUPPORT))
> @@ -59,6 +62,11 @@ EXPORT_SYMBOL_GPL(sdhci_uhs2_dump_regs);
> * *
> \*****************************************************************************/
>
> +static inline u16 uhs2_dev_cmd(struct mmc_command *cmd)
> +{
> + return be16_to_cpu((__be16)cmd->uhs2_cmd->arg) & UHS2_ARG_IOADR_MASK;
> +}
> +
> static inline int mmc_opt_regulator_set_ocr(struct mmc_host *mmc,
> struct regulator *supply,
> unsigned short vdd_bit)
> @@ -452,6 +460,415 @@ static int sdhci_uhs2_control(struct mmc_host *mmc, enum sd_uhs2_operation op)
> return err;
> }
>
> +/*****************************************************************************\
> + * *
> + * Core functions *
> + * *
> +\*****************************************************************************/
> +
> +static void sdhci_uhs2_prepare_data(struct sdhci_host *host,
> + struct mmc_command *cmd)
> +{
> + struct mmc_data *data = cmd->data;
> +
> + sdhci_initialize_data(host, data);
> +
> + sdhci_prepare_dma(host, data);
> +
> + sdhci_writew(host, data->blksz, SDHCI_UHS2_BLOCK_SIZE);
> + sdhci_writew(host, data->blocks, SDHCI_UHS2_BLOCK_COUNT);
> +}
> +
> +static void sdhci_uhs2_finish_data(struct sdhci_host *host)
> +{
> + struct mmc_data *data = host->data;
> +
> + __sdhci_finish_data_common(host);
> +
> + __sdhci_finish_mrq(host, data->mrq);
> +}
> +
> +static void sdhci_uhs2_set_transfer_mode(struct sdhci_host *host,
> + struct mmc_command *cmd)
> +{
> + u16 mode;
> + struct mmc_data *data = cmd->data;
> +
> + if (!data) {
> + /* clear Auto CMD settings for no data CMDs */
> + if (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_TRANS_ABORT) {
> + mode = 0;
> + } else {
> + mode = sdhci_readw(host, SDHCI_UHS2_TRANS_MODE);
> + if (cmd->opcode == MMC_STOP_TRANSMISSION || cmd->opcode == MMC_ERASE)
> + mode |= SDHCI_UHS2_TRNS_WAIT_EBSY;
> + else
> + /* send status mode */
> + if (cmd->opcode == MMC_SEND_STATUS)
> + mode = 0;
> + }
> +
> + DBG("UHS2 no data trans mode is 0x%x.\n", mode);
> +
> + sdhci_writew(host, mode, SDHCI_UHS2_TRANS_MODE);
> + return;
> + }
> +
> + WARN_ON(!host->data);
> +
> + mode = SDHCI_UHS2_TRNS_BLK_CNT_EN | SDHCI_UHS2_TRNS_WAIT_EBSY;
> + if (data->flags & MMC_DATA_WRITE)
> + mode |= SDHCI_UHS2_TRNS_DATA_TRNS_WRT;
> +
> + if (data->blocks == 1 &&
> + data->blksz != 512 &&
> + cmd->opcode != MMC_READ_SINGLE_BLOCK &&
> + cmd->opcode != MMC_WRITE_BLOCK) {
> + mode &= ~SDHCI_UHS2_TRNS_BLK_CNT_EN;
> + mode |= SDHCI_UHS2_TRNS_BLK_BYTE_MODE;
> + }
> +
> + if (host->flags & SDHCI_REQ_USE_DMA)
> + mode |= SDHCI_UHS2_TRNS_DMA;
> +
> + if ((host->mmc->uhs2_ios.is_2L_HD_mode) && !cmd->uhs2_tmode0_flag)
> + mode |= SDHCI_UHS2_TRNS_2L_HD;
> +
> + sdhci_writew(host, mode, SDHCI_UHS2_TRANS_MODE);
> +
> + DBG("UHS2 trans mode is 0x%x.\n", mode);
> +}
> +
> +static void __sdhci_uhs2_send_command(struct sdhci_host *host,
> + struct mmc_command *cmd)
> +{
> + int i, j;
> + int cmd_reg;
> +
> + i = 0;
> + sdhci_writel(host,
> + ((u32)cmd->uhs2_cmd->arg << 16) |
> + (u32)cmd->uhs2_cmd->header,
> + SDHCI_UHS2_CMD_PACKET + i);
> + i += 4;
> +
> + /*
> + * Per spec, playload (config) should be MSB before sending out.
> + * But we don't need convert here because had set payload as
> + * MSB when preparing config read/write commands.
> + */
> + for (j = 0; j < cmd->uhs2_cmd->payload_len / sizeof(u32); j++) {
> + sdhci_writel(host, *(cmd->uhs2_cmd->payload + j), SDHCI_UHS2_CMD_PACKET + i);
> + i += 4;
> + }
> +
> + for ( ; i < SDHCI_UHS2_CMD_PACK_MAX_LEN; i += 4)
> + sdhci_writel(host, 0, SDHCI_UHS2_CMD_PACKET + i);
> +
> + DBG("UHS2 CMD packet_len = %d.\n", cmd->uhs2_cmd->packet_len);
> + for (i = 0; i < cmd->uhs2_cmd->packet_len; i++)
> + DBG("UHS2 CMD_PACKET[%d] = 0x%x.\n", i,
> + sdhci_readb(host, SDHCI_UHS2_CMD_PACKET + i));
> +
> + cmd_reg = FIELD_PREP(SDHCI_UHS2_CMD_PACK_LEN_MASK, cmd->uhs2_cmd->packet_len);
> + if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC)
> + cmd_reg |= SDHCI_UHS2_CMD_DATA;
> + if (cmd->opcode == MMC_STOP_TRANSMISSION)
> + cmd_reg |= SDHCI_UHS2_CMD_CMD12;
> +
> + /* UHS2 Native ABORT */
> + if ((cmd->uhs2_cmd->header & UHS2_NATIVE_PACKET) &&
> + (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_TRANS_ABORT))
> + cmd_reg |= SDHCI_UHS2_CMD_TRNS_ABORT;
> +
> + /* UHS2 Native DORMANT */
> + if ((cmd->uhs2_cmd->header & UHS2_NATIVE_PACKET) &&
> + (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_GO_DORMANT_STATE))
> + cmd_reg |= SDHCI_UHS2_CMD_DORMANT;
> +
> + DBG("0x%x is set to UHS2 CMD register.\n", cmd_reg);
> +
> + sdhci_writew(host, cmd_reg, SDHCI_UHS2_CMD);
> +}
> +
> +static bool sdhci_uhs2_send_command(struct sdhci_host *host,
> + struct mmc_command *cmd)
> +{
> + int flags;
> + u32 mask;
> + unsigned long timeout;
> +
> + WARN_ON(host->cmd);
> +
> + /* Initially, a command has no error */
> + cmd->error = 0;
> +
> + if (cmd->opcode == MMC_STOP_TRANSMISSION)
> + cmd->flags |= MMC_RSP_BUSY;
> +
> + mask = SDHCI_CMD_INHIBIT;
> +
> + if (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask)
> + return false;
> +
> + host->cmd = cmd;
> + host->data_timeout = 0;
> + if (sdhci_data_line_cmd(cmd)) {
> + WARN_ON(host->data_cmd);
> + host->data_cmd = cmd;
> + __sdhci_uhs2_set_timeout(host);
> + }
> +
> + if (cmd->data)
> + sdhci_uhs2_prepare_data(host, cmd);
> +
> + sdhci_uhs2_set_transfer_mode(host, cmd);
> +
> + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
> + WARN_ONCE(1, "Unsupported response type!\n");
> + /*
> + * This does not happen in practice because 136-bit response
> + * commands never have busy waiting, so rather than complicate
> + * the error path, just remove busy waiting and continue.
> + */
> + cmd->flags &= ~MMC_RSP_BUSY;
> + }
> +
> + if (!(cmd->flags & MMC_RSP_PRESENT))
> + flags = SDHCI_CMD_RESP_NONE;
> + else if (cmd->flags & MMC_RSP_136)
> + flags = SDHCI_CMD_RESP_LONG;
> + else if (cmd->flags & MMC_RSP_BUSY)
> + flags = SDHCI_CMD_RESP_SHORT_BUSY;
> + else
> + flags = SDHCI_CMD_RESP_SHORT;
> +
> + if (cmd->flags & MMC_RSP_CRC)
> + flags |= SDHCI_CMD_CRC;
> + if (cmd->flags & MMC_RSP_OPCODE)
> + flags |= SDHCI_CMD_INDEX;
> +
> + timeout = jiffies;
> + if (host->data_timeout)
> + timeout += nsecs_to_jiffies(host->data_timeout);
> + else if (!cmd->data && cmd->busy_timeout > 9000)
> + timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
> + else
> + timeout += 10 * HZ;
> + sdhci_mod_timer(host, cmd->mrq, timeout);
> +
> + __sdhci_uhs2_send_command(host, cmd);
> +
> + return true;
> +}
> +
> +static bool sdhci_uhs2_send_command_retry(struct sdhci_host *host,
> + struct mmc_command *cmd,
> + unsigned long flags)
> + __releases(host->lock)
> + __acquires(host->lock)
> +{
> + struct mmc_command *deferred_cmd = host->deferred_cmd;
> + int timeout = 10; /* Approx. 10 ms */
> + bool present;
> +
> + while (!sdhci_uhs2_send_command(host, cmd)) {
> + if (!timeout--) {
> + pr_err("%s: Controller never released inhibit bit(s).\n",
> + mmc_hostname(host->mmc));
> + sdhci_dumpregs(host);
> + cmd->error = -EIO;
> + return false;
> + }
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + usleep_range(1000, 1250);
> +
> + present = host->mmc->ops->get_cd(host->mmc);
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + /* A deferred command might disappear, handle that */
> + if (cmd == deferred_cmd && cmd != host->deferred_cmd)
> + return true;
> +
> + if (sdhci_present_error(host, cmd, present))
> + return false;
> + }
> +
> + if (cmd == host->deferred_cmd)
> + host->deferred_cmd = NULL;
> +
> + return true;
> +}
> +
> +static void __sdhci_uhs2_finish_command(struct sdhci_host *host)
> +{
> + struct mmc_command *cmd = host->cmd;
> + u8 resp;
> + u8 ecode;
> + bool bReadA0 = 0;
> + int i;
> +
> + if (host->mmc->card) {
> + if (host->mmc->card->uhs2_state & MMC_UHS2_INITIALIZED) {
> + resp = sdhci_readb(host, SDHCI_UHS2_RESPONSE + 2);
> + if (resp & UHS2_RES_NACK_MASK) {
> + ecode = (resp >> UHS2_RES_ECODE_POS) & UHS2_RES_ECODE_MASK;
> + pr_err("%s: NACK is got, ECODE=0x%x.\n",
> + mmc_hostname(host->mmc), ecode);
> + }
> + bReadA0 = 1;
> + }
> + }
> +
> + if (cmd->uhs2_resp &&
> + cmd->uhs2_resp_len && cmd->uhs2_resp_len <= 20) {
> + /* Get whole response of some native CCMD, like
> + * DEVICE_INIT, ENUMERATE.
> + */
> + for (i = 0; i < cmd->uhs2_resp_len; i++)
> + cmd->uhs2_resp[i] = sdhci_readb(host, SDHCI_UHS2_RESPONSE + i);
> + } else {
> + /* Get SD CMD response and Payload for some read
> + * CCMD, like INQUIRY_CFG.
> + */
> + /* Per spec (p136), payload field is divided into
> + * a unit of DWORD and transmission order within
> + * a DWORD is big endian.
> + */
> + if (!bReadA0)
> + sdhci_readl(host, SDHCI_UHS2_RESPONSE);
> + for (i = 4; i < 20; i += 4) {
> + cmd->resp[i / 4 - 1] =
> + (sdhci_readb(host,
> + SDHCI_UHS2_RESPONSE + i) << 24) |
> + (sdhci_readb(host,
> + SDHCI_UHS2_RESPONSE + i + 1)
> + << 16) |
> + (sdhci_readb(host,
> + SDHCI_UHS2_RESPONSE + i + 2)
> + << 8) |
> + sdhci_readb(host, SDHCI_UHS2_RESPONSE + i + 3);
> + }
> + }
> +}
> +
> +static void sdhci_uhs2_finish_command(struct sdhci_host *host)
> +{
> + struct mmc_command *cmd = host->cmd;
> +
> + __sdhci_uhs2_finish_command(host);
> +
> + host->cmd = NULL;
> +
> + if (cmd->mrq->cap_cmd_during_tfr && cmd == cmd->mrq->cmd)
> + mmc_command_done(host->mmc, cmd->mrq);
> +
> + /*
> + * The host can send and interrupt when the busy state has
> + * ended, allowing us to wait without wasting CPU cycles.
> + * The busy signal uses DAT0 so this is similar to waiting
> + * for data to complete.
> + *
> + * Note: The 1.0 specification is a bit ambiguous about this
> + * feature so there might be some problems with older
> + * controllers.
> + */
> + if (cmd->flags & MMC_RSP_BUSY) {
> + if (cmd->data) {
> + DBG("Cannot wait for busy signal when also doing a data transfer");
> + } else if (!(host->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) &&
> + cmd == host->data_cmd) {
> + /* Command complete before busy is ended */
> + return;
> + }
> + }
> +
> + /* Processed actual command. */
> + if (host->data && host->data_early)
> + sdhci_uhs2_finish_data(host);
> +
> + if (!cmd->data)
> + __sdhci_finish_mrq(host, cmd->mrq);
> +}
> +
> +void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
> +{
> + struct sdhci_host *host = mmc_priv(mmc);
> + struct mmc_command *cmd;
> + unsigned long flags;
> + bool present;
> +
> + if (!(sdhci_uhs2_mode(host))) {
> + sdhci_request(mmc, mrq);
> + return;
> + }
> +
> + mrq->stop = NULL;
> + mrq->sbc = NULL;
> + if (mrq->data)
> + mrq->data->stop = NULL;
> +
> + /* Firstly check card presence */
> + present = mmc->ops->get_cd(mmc);
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (sdhci_present_error(host, mrq->cmd, present))
> + goto out_finish;
> +
> + cmd = mrq->cmd;
> +
> + if (!sdhci_uhs2_send_command(host, cmd))
> + goto out_finish;
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + return;
> +
> +out_finish:
> + sdhci_finish_mrq(host, mrq);
> + spin_unlock_irqrestore(&host->lock, flags);
> +}
> +EXPORT_SYMBOL_GPL(sdhci_uhs2_request);
> +
> +int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq)
> +{
> + struct sdhci_host *host = mmc_priv(mmc);
> + struct mmc_command *cmd;
> + unsigned long flags;
> + int ret = 0;
> +
> + if (!host->mmc->flags & MMC_UHS2_SUPPORT)
> + return sdhci_request_atomic(mmc, mrq);
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (sdhci_present_error(host, mrq->cmd, true)) {
> + sdhci_finish_mrq(host, mrq);
> + goto out_finish;
> + }
> +
> + cmd = mrq->cmd;
> +
> + /*
> + * The HSQ may send a command in interrupt context without polling
> + * the busy signaling, which means we should return BUSY if controller
> + * has not released inhibit bits to allow HSQ trying to send request
> + * again in non-atomic context. So we should not finish this request
> + * here.
> + */
> + if (!sdhci_uhs2_send_command(host, cmd))
> + ret = -EBUSY;
> +
> +out_finish:
> + spin_unlock_irqrestore(&host->lock, flags);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(sdhci_uhs2_request_atomic);
> +
> /*****************************************************************************\
> * *
> * Driver init/exit *
> @@ -581,6 +998,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> sdhci_uhs2_start_signal_voltage_switch;
> host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
> host->mmc_host_ops.uhs2_control = sdhci_uhs2_control;
> + host->mmc_host_ops.request = sdhci_uhs2_request;
>
> return 0;
> }
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 3656e66fcca7..c547fc3b8b47 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -47,8 +47,6 @@
> static unsigned int debug_quirks = 0;
> static unsigned int debug_quirks2;
>
> -static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
> -
> void sdhci_dumpregs(struct sdhci_host *host)
> {
> SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n");
> @@ -147,10 +145,11 @@ void sdhci_enable_v4_mode(struct sdhci_host *host)
> }
> EXPORT_SYMBOL_GPL(sdhci_enable_v4_mode);
>
> -static inline bool sdhci_data_line_cmd(struct mmc_command *cmd)
> +bool sdhci_data_line_cmd(struct mmc_command *cmd)
> {
> return cmd->data || cmd->flags & MMC_RSP_BUSY;
> }
> +EXPORT_SYMBOL_GPL(sdhci_data_line_cmd);
>
> static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
> {
> @@ -424,7 +423,7 @@ static void __sdhci_led_activate(struct sdhci_host *host)
> sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
> }
>
> -static void __sdhci_led_deactivate(struct sdhci_host *host)
> +void __sdhci_led_deactivate(struct sdhci_host *host)
> {
> u8 ctrl;
>
> @@ -435,6 +434,7 @@ static void __sdhci_led_deactivate(struct sdhci_host *host)
> ctrl &= ~SDHCI_CTRL_LED;
> sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
> }
> +EXPORT_SYMBOL_GPL(__sdhci_led_deactivate);
Do not need to export __sdhci_led_deactivate
>
> #if IS_REACHABLE(CONFIG_LEDS_CLASS)
> static void sdhci_led_control(struct led_classdev *led,
> @@ -513,14 +513,15 @@ static inline void sdhci_led_deactivate(struct sdhci_host *host)
>
> #endif
>
> -static void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq,
> - unsigned long timeout)
> +void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq,
> + unsigned long timeout)
> {
> if (sdhci_data_line_cmd(mrq->cmd))
> mod_timer(&host->data_timer, timeout);
> else
> mod_timer(&host->timer, timeout);
> }
> +EXPORT_SYMBOL_GPL(sdhci_mod_timer);
>
> static void sdhci_del_timer(struct sdhci_host *host, struct mmc_request *mrq)
> {
> @@ -1087,8 +1088,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
> __sdhci_set_timeout(host, cmd);
> }
>
> -static void sdhci_initialize_data(struct sdhci_host *host,
> - struct mmc_data *data)
> +void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data)
> {
> WARN_ON(host->data);
>
> @@ -1101,6 +1101,7 @@ static void sdhci_initialize_data(struct sdhci_host *host,
> host->data_early = 0;
> host->data->bytes_xfered = 0;
> }
> +EXPORT_SYMBOL_GPL(sdhci_initialize_data);
>
> static inline void sdhci_set_block_info(struct sdhci_host *host,
> struct mmc_data *data)
> @@ -1123,12 +1124,8 @@ static inline void sdhci_set_block_info(struct sdhci_host *host,
> }
> }
>
> -static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> +void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data)
> {
> - struct mmc_data *data = cmd->data;
> -
> - sdhci_initialize_data(host, data);
> -
> if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
> struct scatterlist *sg;
> unsigned int length_mask, offset_mask;
> @@ -1213,6 +1210,16 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> }
>
> sdhci_set_transfer_irqs(host);
> +}
> +EXPORT_SYMBOL_GPL(sdhci_prepare_dma);
> +
> +static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> +{
> + struct mmc_data *data = cmd->data;
> +
> + sdhci_initialize_data(host, data);
> +
> + sdhci_prepare_dma(host, data);
>
> sdhci_set_block_info(host, data);
> }
> @@ -1255,8 +1262,7 @@ static struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
> return data->flags & MMC_DATA_WRITE ? host->tx_chan : host->rx_chan;
> }
>
> -static int sdhci_external_dma_setup(struct sdhci_host *host,
> - struct mmc_command *cmd)
> +int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd)
> {
> int ret, i;
> enum dma_transfer_direction dir;
> @@ -1309,8 +1315,9 @@ static int sdhci_external_dma_setup(struct sdhci_host *host,
>
> return ret;
> }
> +EXPORT_SYMBOL_GPL(sdhci_external_dma_setup);
Do not need to export sdhci_external_dma_setup
>
> -static void sdhci_external_dma_release(struct sdhci_host *host)
> +void sdhci_external_dma_release(struct sdhci_host *host)
> {
> if (host->tx_chan) {
> dma_release_channel(host->tx_chan);
> @@ -1324,9 +1331,10 @@ static void sdhci_external_dma_release(struct sdhci_host *host)
>
> sdhci_switch_external_dma(host, false);
> }
> +EXPORT_SYMBOL_GPL(sdhci_external_dma_release);
Do not need to export sdhci_external_dma_release
>
> -static void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
> - struct mmc_command *cmd)
> +void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
> + struct mmc_command *cmd)
> {
> struct mmc_data *data = cmd->data;
>
> @@ -1337,6 +1345,7 @@ static void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
>
> sdhci_set_block_info(host, data);
> }
> +EXPORT_SYMBOL(__sdhci_external_dma_prepare_data);
Do not need to export __sdhci_external_dma_prepare_data
>
> static void sdhci_external_dma_prepare_data(struct sdhci_host *host,
> struct mmc_command *cmd)
> @@ -1351,8 +1360,8 @@ static void sdhci_external_dma_prepare_data(struct sdhci_host *host,
> }
> }
>
> -static void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
> - struct mmc_command *cmd)
> +void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
> + struct mmc_command *cmd)
> {
> struct dma_chan *chan;
>
> @@ -1363,6 +1372,7 @@ static void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
> if (chan)
> dma_async_issue_pending(chan);
> }
> +EXPORT_SYMBOL_GPL(sdhci_external_dma_pre_transfer);
Do not need to export sdhci_external_dma_pre_transfer
>
> #else
>
> @@ -1414,8 +1424,7 @@ static inline bool sdhci_auto_cmd23(struct sdhci_host *host,
> return mrq->sbc && (host->flags & SDHCI_AUTO_CMD23);
> }
>
> -static inline bool sdhci_manual_cmd23(struct sdhci_host *host,
> - struct mmc_request *mrq)
> +static inline bool sdhci_manual_cmd23(struct sdhci_host *host, struct mmc_request *mrq)
Please do not make whitespace changes if nothing else changes
> {
> return mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23);
> }
> @@ -1530,7 +1539,7 @@ static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
> WARN_ON(i >= SDHCI_MAX_MRQS);
> }
>
> -static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> +void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> {
> if (host->cmd && host->cmd->mrq == mrq)
> host->cmd = NULL;
> @@ -1554,15 +1563,17 @@ static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> if (!sdhci_has_requests(host))
> sdhci_led_deactivate(host);
> }
> +EXPORT_SYMBOL_GPL(__sdhci_finish_mrq);
>
> -static void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> +void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> {
> __sdhci_finish_mrq(host, mrq);
>
> queue_work(host->complete_wq, &host->complete_work);
> }
> +EXPORT_SYMBOL_GPL(sdhci_finish_mrq);
>
> -static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
> +void __sdhci_finish_data_common(struct sdhci_host *host)
> {
> struct mmc_command *data_cmd = host->data_cmd;
> struct mmc_data *data = host->data;
> @@ -1596,6 +1607,14 @@ static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
> data->bytes_xfered = 0;
> else
> data->bytes_xfered = data->blksz * data->blocks;
> +}
> +EXPORT_SYMBOL_GPL(__sdhci_finish_data_common);
> +
> +static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
> +{
> + struct mmc_data *data = host->data;
> +
> + __sdhci_finish_data_common(host);
>
> /*
> * Need to send CMD12 if -
> @@ -1681,8 +1700,6 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> sdhci_prepare_data(host, cmd);
> }
>
> - sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
> -
> sdhci_set_transfer_mode(host, cmd);
>
> if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
> @@ -1725,13 +1742,15 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> if (host->use_external_dma)
> sdhci_external_dma_pre_transfer(host, cmd);
>
> + sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
If this move of "sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT)"
is intentional, it should be a separate patch.
> +
> sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
>
> return true;
> }
>
> -static bool sdhci_present_error(struct sdhci_host *host,
> - struct mmc_command *cmd, bool present)
> +bool sdhci_present_error(struct sdhci_host *host,
> + struct mmc_command *cmd, bool present)
> {
> if (!present || host->flags & SDHCI_DEVICE_DEAD) {
> cmd->error = -ENOMEDIUM;
> @@ -1740,6 +1759,7 @@ static bool sdhci_present_error(struct sdhci_host *host,
>
> return false;
> }
> +EXPORT_SYMBOL_GPL(sdhci_present_error);
>
> static bool sdhci_send_command_retry(struct sdhci_host *host,
> struct mmc_command *cmd,
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index c2f989dc2361..3b87fcae1294 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -831,6 +831,20 @@ static inline void sdhci_read_caps(struct sdhci_host *host)
> __sdhci_read_caps(host, NULL, NULL, NULL);
> }
>
> +bool sdhci_data_line_cmd(struct mmc_command *cmd);
> +void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, unsigned long timeout);
> +void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data);
> +void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data);
> +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA)
> +int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd);
> +void sdhci_external_dma_release(struct sdhci_host *host);
> +void __sdhci_external_dma_prepare_data(struct sdhci_host *host, struct mmc_command *cmd);
> +void sdhci_external_dma_pre_transfer(struct sdhci_host *host, struct mmc_command *cmd);
> +#endif
external_dma fns do not need export
> +void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> +void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> +void __sdhci_finish_data_common(struct sdhci_host *host);
> +bool sdhci_present_error(struct sdhci_host *host, struct mmc_command *cmd, bool present);
> u16 sdhci_calc_clk(struct sdhci_host *host, unsigned int clock,
> unsigned int *actual_clock);
> void sdhci_set_clock(struct sdhci_host *host, unsigned int clock);
On 13/12/22 11:00, Victor Shih wrote:
> This is a UHS-II version of sdhci's add_host/remove_host operation.
> Any sdhci drivers which are capable of handling UHS-II cards must
> call those functions instead of the corresponding sdhci's.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/sdhci-uhs2.c | 171 ++++++++++++++++++++++++++++++++++
> drivers/mmc/host/sdhci-uhs2.h | 2 +
> drivers/mmc/host/sdhci.c | 28 ++++--
> drivers/mmc/host/sdhci.h | 12 +++
> 4 files changed, 204 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> index 769cf88b21cf..5349173b4f90 100644
> --- a/drivers/mmc/host/sdhci-uhs2.c
> +++ b/drivers/mmc/host/sdhci-uhs2.c
> @@ -17,6 +17,7 @@
> #include <linux/ktime.h>
> #include <linux/mmc/mmc.h>
> #include <linux/dmaengine.h>
> +#include <linux/regulator/consumer.h>
>
> #include "sdhci.h"
> #include "sdhci-uhs2.h"
> @@ -1015,6 +1016,176 @@ static irqreturn_t sdhci_uhs2_thread_irq(int irq, void *dev_id)
> return IRQ_HANDLED;
> }
>
> +/*****************************************************************************\
> + *
> + * Device allocation/registration *
> + * *
> +\*****************************************************************************/
> +
> +static int __sdhci_uhs2_add_host_v4(struct sdhci_host *host, u32 caps1)
> +{
> + struct mmc_host *mmc;
> + u32 max_current_caps2;
> +
> + if (host->version < SDHCI_SPEC_400)
> + return 0;
> +
> + mmc = host->mmc;
> +
> + /* Support UHS2 */
> + if (caps1 & SDHCI_SUPPORT_UHS2)
> + mmc->caps2 |= MMC_CAP2_SD_UHS2;
> +
> + max_current_caps2 = sdhci_readl(host, SDHCI_MAX_CURRENT_1);
> +
> + if ((caps1 & SDHCI_CAN_VDD2_180) &&
> + !max_current_caps2 &&
> + !IS_ERR(mmc->supply.vmmc2)) {
> + /* UHS2 - VDD2 */
> + int curr = regulator_get_current_limit(mmc->supply.vmmc2);
> +
> + if (curr > 0) {
> + /* convert to SDHCI_MAX_CURRENT format */
> + curr = curr / 1000; /* convert to mA */
> + curr = curr / SDHCI_MAX_CURRENT_MULTIPLIER;
> + curr = min_t(u32, curr, SDHCI_MAX_CURRENT_LIMIT);
> + max_current_caps2 = curr;
> + }
> + }
> +
> + if (caps1 & SDHCI_CAN_VDD2_180) {
> + mmc->ocr_avail_uhs2 |= MMC_VDD2_165_195;
> + /*
> + * UHS2 doesn't require this. Only UHS-I bus needs to set
> + * max current.
> + */
> + mmc->max_current_180_vdd2 = (max_current_caps2 &
> + SDHCI_MAX_CURRENT_VDD2_180_MASK) *
> + SDHCI_MAX_CURRENT_MULTIPLIER;
> + } else {
> + mmc->caps2 &= ~MMC_CAP2_SD_UHS2;
> + }
> +
> + return 0;
> +}
> +
> +static int sdhci_uhs2_host_ops_init(struct sdhci_host *host);
> +
> +static int __sdhci_uhs2_add_host(struct sdhci_host *host)
> +{
> + unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
> + struct mmc_host *mmc = host->mmc;
> + int ret;
> +
> + if ((mmc->caps2 & MMC_CAP2_CQE) &&
> + (host->quirks & SDHCI_QUIRK_BROKEN_CQE)) {
> + mmc->caps2 &= ~MMC_CAP2_CQE;
> + mmc->cqe_ops = NULL;
> + }
> +
> + host->complete_wq = alloc_workqueue("sdhci", flags, 0);
> + if (!host->complete_wq)
> + return -ENOMEM;
> +
> + INIT_WORK(&host->complete_work, host->complete_work_fn);
> +
> + timer_setup(&host->timer, sdhci_timeout_timer, 0);
> + timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
> +
> + init_waitqueue_head(&host->buf_ready_int);
> +
> + sdhci_init(host, 0);
> +
> + ret = request_threaded_irq(host->irq, sdhci_irq,
> + host->thread_irq_fn,
> + IRQF_SHARED, mmc_hostname(mmc), host);
> + if (ret) {
> + pr_err("%s: Failed to request IRQ %d: %d\n",
> + mmc_hostname(mmc), host->irq, ret);
> + goto unwq;
> + }
> +
> + ret = mmc_add_host(mmc);
> + if (ret)
> + return 1;
> +
> + pr_info("%s: SDHCI controller on %s [%s] using %s\n",
> + mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
> + host->use_external_dma ? "External DMA" :
> + (host->flags & SDHCI_USE_ADMA) ?
> + (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" :
> + (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");
> +
> + sdhci_enable_card_detection(host);
> +
> + return 0;
> +
> +unwq:
> + destroy_workqueue(host->complete_wq);
> +
> + return ret;
> +}
> +
> +static void __sdhci_uhs2_remove_host(struct sdhci_host *host, int dead)
> +{
> + if (!sdhci_uhs2_mode(host))
> + return;
> +
> + if (!dead)
> + sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_FULL);
> +}
> +
> +int sdhci_uhs2_add_host(struct sdhci_host *host)
> +{
> + struct mmc_host *mmc = host->mmc;
> + int ret;
> +
> + ret = sdhci_setup_host(host);
> + if (ret)
> + return ret;
> +
> + if (host->version >= SDHCI_SPEC_400) {
> + ret = __sdhci_uhs2_add_host_v4(host, host->caps1);
> + if (ret)
> + goto cleanup;
> + }
> +
> + if ((mmc->caps2 & MMC_CAP2_SD_UHS2) && !host->v4_mode)
> + /* host doesn't want to enable UHS2 support */
> + /* FIXME: Do we have to do some cleanup here? */
> + mmc->caps2 &= ~MMC_CAP2_SD_UHS2;
> +
> + /* overwrite ops */
> + if (mmc->caps2 & MMC_CAP2_SD_UHS2)
> + sdhci_uhs2_host_ops_init(host);
> +
> + host->complete_work_fn = sdhci_uhs2_complete_work;
> + host->thread_irq_fn = sdhci_uhs2_thread_irq;
> +
> + ret = __sdhci_uhs2_add_host(host);
Please just use __sdhci_add_host() and __sdhci_uhs2_add_host()
is not needed. i.e.
/* LED support not implemented for UHS2 */
host->quirks |= SDHCI_QUIRK_NO_LED;
ret = __sdhci_add_host(host);
> + if (ret)
> + goto cleanup2;
> +
> + return 0;
> +
> +cleanup2:
> + if (host->version >= SDHCI_SPEC_400)
> + __sdhci_uhs2_remove_host(host, 0);
> +cleanup:
> + sdhci_cleanup_host(host);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(sdhci_uhs2_add_host);
> +
> +void sdhci_uhs2_remove_host(struct sdhci_host *host, int dead)
> +{
> + __sdhci_uhs2_remove_host(host, dead);
> +
> + sdhci_remove_host(host, dead);
> +}
> +EXPORT_SYMBOL_GPL(sdhci_uhs2_remove_host);
> +
> void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
> {
> struct sdhci_host *host = mmc_priv(mmc);
> diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
> index 64b7f356c9fc..b2048c48d056 100644
> --- a/drivers/mmc/host/sdhci-uhs2.h
> +++ b/drivers/mmc/host/sdhci-uhs2.h
> @@ -182,5 +182,7 @@ void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
> void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq);
> int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq);
> u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask);
> +int sdhci_uhs2_add_host(struct sdhci_host *host);
> +void sdhci_uhs2_remove_host(struct sdhci_host *host, int dead);
>
> #endif /* __SDHCI_UHS2_H */
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index 6c67b79102eb..82ad35f4d1b4 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -175,10 +175,11 @@ static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
> sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
> }
>
> -static void sdhci_enable_card_detection(struct sdhci_host *host)
> +void sdhci_enable_card_detection(struct sdhci_host *host)
> {
> sdhci_set_card_detection(host, true);
> }
> +EXPORT_SYMBOL_GPL(sdhci_enable_card_detection);
sdhci_enable_card_detection() does not need to be exported
when __sdhci_uhs2_add_host() is not needed anymore
>
> static void sdhci_disable_card_detection(struct sdhci_host *host)
> {
> @@ -369,7 +370,7 @@ static void sdhci_config_dma(struct sdhci_host *host)
> sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
> }
>
> -static void sdhci_init(struct sdhci_host *host, int soft)
> +void sdhci_init(struct sdhci_host *host, int soft)
> {
> struct mmc_host *mmc = host->mmc;
> unsigned long flags;
> @@ -395,6 +396,7 @@ static void sdhci_init(struct sdhci_host *host, int soft)
> mmc->ops->set_ios(mmc, &mmc->ios);
> }
> }
> +EXPORT_SYMBOL_GPL(sdhci_init);
Does not need to be exported when __sdhci_uhs2_add_host()
is not needed anymore
>
> static void sdhci_reinit(struct sdhci_host *host)
> {
> @@ -458,7 +460,7 @@ static void sdhci_led_control(struct led_classdev *led,
> spin_unlock_irqrestore(&host->lock, flags);
> }
>
> -static int sdhci_led_register(struct sdhci_host *host)
> +int sdhci_led_register(struct sdhci_host *host)
> {
> struct mmc_host *mmc = host->mmc;
>
> @@ -475,14 +477,16 @@ static int sdhci_led_register(struct sdhci_host *host)
>
> return led_classdev_register(mmc_dev(mmc), &host->led);
> }
> +EXPORT_SYMBOL_GPL(sdhci_led_register);
sdhci_led_register does not need export
>
> -static void sdhci_led_unregister(struct sdhci_host *host)
> +void sdhci_led_unregister(struct sdhci_host *host)
> {
> if (host->quirks & SDHCI_QUIRK_NO_LED)
> return;
>
> led_classdev_unregister(&host->led);
> }
> +EXPORT_SYMBOL_GPL(sdhci_led_unregister);
sdhci_led_unregister does not need export
>
> static inline void sdhci_led_activate(struct sdhci_host *host)
> {
> @@ -3265,7 +3269,7 @@ void sdhci_complete_work(struct work_struct *work)
> }
> EXPORT_SYMBOL_GPL(sdhci_complete_work);
>
> -static void sdhci_timeout_timer(struct timer_list *t)
> +void sdhci_timeout_timer(struct timer_list *t)
> {
> struct sdhci_host *host;
> unsigned long flags;
> @@ -3286,8 +3290,9 @@ static void sdhci_timeout_timer(struct timer_list *t)
>
> spin_unlock_irqrestore(&host->lock, flags);
> }
> +EXPORT_SYMBOL_GPL(sdhci_timeout_timer);
sdhci_timeout_timer() does not need export
when __sdhci_uhs2_add_host() is not needed anymore
>
> -static void sdhci_timeout_data_timer(struct timer_list *t)
> +void sdhci_timeout_data_timer(struct timer_list *t)
> {
> struct sdhci_host *host;
> unsigned long flags;
> @@ -3318,6 +3323,7 @@ static void sdhci_timeout_data_timer(struct timer_list *t)
>
> spin_unlock_irqrestore(&host->lock, flags);
> }
> +EXPORT_SYMBOL_GPL(sdhci_timeout_data_timer);
sdhci_timeout_data_timer() does not need export
when __sdhci_uhs2_add_host() is not needed anymore
>
> /*****************************************************************************\
> * *
> @@ -3577,7 +3583,7 @@ static inline bool sdhci_defer_done(struct sdhci_host *host,
> data->host_cookie == COOKIE_MAPPED);
> }
>
> -static irqreturn_t sdhci_irq(int irq, void *dev_id)
> +irqreturn_t sdhci_irq(int irq, void *dev_id)
> {
> struct mmc_request *mrqs_done[SDHCI_MAX_MRQS] = {0};
> irqreturn_t result = IRQ_NONE;
> @@ -3718,6 +3724,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
>
> return result;
> }
> +EXPORT_SYMBOL_GPL(sdhci_irq);
sdhci_irq() does not need export
when __sdhci_uhs2_add_host() is not needed anymore
>
> irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
> {
> @@ -4122,6 +4129,9 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,
>
> host->max_timeout_count = 0xE;
>
> + host->complete_work_fn = sdhci_complete_work;
> + host->thread_irq_fn = sdhci_thread_irq;
> +
> return host;
> }
>
> @@ -4874,7 +4884,7 @@ int __sdhci_add_host(struct sdhci_host *host)
> if (!host->complete_wq)
> return -ENOMEM;
>
> - INIT_WORK(&host->complete_work, sdhci_complete_work);
> + INIT_WORK(&host->complete_work, host->complete_work_fn);
>
> timer_setup(&host->timer, sdhci_timeout_timer, 0);
> timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
> @@ -4883,7 +4893,7 @@ int __sdhci_add_host(struct sdhci_host *host)
>
> sdhci_init(host, 0);
>
> - ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
> + ret = request_threaded_irq(host->irq, sdhci_irq, host->thread_irq_fn,
> IRQF_SHARED, mmc_hostname(mmc), host);
> if (ret) {
> pr_err("%s: Failed to request IRQ %d: %d\n",
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index 4979af7cabca..5f090b5ecc61 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -629,6 +629,9 @@ struct sdhci_host {
> struct timer_list timer; /* Timer for timeouts */
> struct timer_list data_timer; /* Timer for data timeouts */
>
> + void (*complete_work_fn)(struct work_struct *work);
> + irqreturn_t (*thread_irq_fn)(int irq, void *dev_id);
> +
> #if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA)
> struct dma_chan *rx_chan;
> struct dma_chan *tx_chan;
> @@ -832,6 +835,12 @@ static inline void sdhci_read_caps(struct sdhci_host *host)
> }
>
> bool sdhci_data_line_cmd(struct mmc_command *cmd);
> +void sdhci_enable_card_detection(struct sdhci_host *host);
> +void sdhci_init(struct sdhci_host *host, int soft);
> +#if IS_REACHABLE(CONFIG_LEDS_CLASS)
> +int sdhci_led_register(struct sdhci_host *host);
> +void sdhci_led_unregister(struct sdhci_host *host);
> +#endif
Export of sdhci_led_register and sdhci_led_unregister
is not needed
> void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, unsigned long timeout);
> void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data);
> void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data);
> @@ -875,6 +884,9 @@ int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
> void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq);
> void sdhci_complete_work(struct work_struct *work);
> +void sdhci_timeout_timer(struct timer_list *t);
> +void sdhci_timeout_data_timer(struct timer_list *t);
sdhci_timeout_timer() and sdhci_timeout_data_timer()
do not need export when __sdhci_uhs2_add_host()
is not needed anymore
> +irqreturn_t sdhci_irq(int irq, void *dev_id);
sdhci_irq() does not need export
when __sdhci_uhs2_add_host() is not needed anymore
> irqreturn_t sdhci_thread_irq(int irq, void *dev_id);
> void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
> dma_addr_t addr, int len, unsigned int cmd);
On 13/12/22 11:00, Victor Shih wrote:
> From: Ben Chuang <[email protected]>
>
> This "post" hook for mmc_attach_sd(), uhs2_post_attach_sd, will be required
> to enable UHS-II support, at least, on GL9755.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> ---
> drivers/mmc/host/sdhci.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index e5f64b38e339..4aac22b58d79 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -728,6 +728,7 @@ struct sdhci_ops {
> void (*dump_vendor_regs)(struct sdhci_host *host);
> void (*dump_uhs2_regs)(struct sdhci_host *host);
> void (*uhs2_pre_detect_init)(struct sdhci_host *host);
> + void (*uhs2_post_attach_sd)(struct sdhci_host *host);
This gets called already in "mmc: sdhci-uhs2: add uhs2_control()
to initialise the interface"
Either move that line to this patch or vice versa.
Please check compiling after each patch is applied.
> };
>
> #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
On 13/12/22 11:00, Victor Shih wrote:
> This is a UHS-II version of sdhci's request() operation.
> It handles UHS-II related command interrupts and errors.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/sdhci-uhs2.c | 223 +++++++++++++++++++++++++++++++++-
> drivers/mmc/host/sdhci-uhs2.h | 3 +
> drivers/mmc/host/sdhci.c | 111 +++++++++--------
> drivers/mmc/host/sdhci.h | 6 +
> 4 files changed, 294 insertions(+), 49 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> index 7a34f66c4215..769cf88b21cf 100644
> --- a/drivers/mmc/host/sdhci-uhs2.c
> +++ b/drivers/mmc/host/sdhci-uhs2.c
> @@ -16,6 +16,7 @@
> #include <linux/bitfield.h>
> #include <linux/ktime.h>
> #include <linux/mmc/mmc.h>
> +#include <linux/dmaengine.h>
Looks like linux/dmaengine.h is not needed
>
> #include "sdhci.h"
> #include "sdhci-uhs2.h"
> @@ -794,6 +795,226 @@ static void sdhci_uhs2_finish_command(struct sdhci_host *host)
> __sdhci_finish_mrq(host, cmd->mrq);
> }
>
> +/*****************************************************************************\
> + * *
> + * Request done *
> + * *
> +\*****************************************************************************/
> +
> +static bool sdhci_uhs2_request_done(struct sdhci_host *host)
> +{
> + unsigned long flags;
> + struct mmc_request *mrq;
> + int i;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + for (i = 0; i < SDHCI_MAX_MRQS; i++) {
> + mrq = host->mrqs_done[i];
> + if (mrq)
> + break;
> + }
> +
> + if (!mrq) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + return true;
> + }
> +
> + /*
> + * Always unmap the data buffers if they were mapped by
> + * sdhci_prepare_data() whenever we finish with a request.
> + * This avoids leaking DMA mappings on error.
> + */
> + if (host->flags & SDHCI_REQ_USE_DMA)
> + sdhci_request_done_dma(host, mrq);
> +
> + /*
> + * The controller needs a reset of internal state machines
> + * upon error conditions.
> + */
> + if (sdhci_needs_reset(host, mrq)) {
> + /*
> + * Do not finish until command and data lines are available for
> + * reset. Note there can only be one other mrq, so it cannot
> + * also be in mrqs_done, otherwise host->cmd and host->data_cmd
> + * would both be null.
> + */
> + if (host->cmd || host->data_cmd) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + return true;
> + }
> +
> + /* Some controllers need this kick or reset won't work here */
> + if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET)
> + /* This is to force an update */
> + host->ops->set_clock(host, host->clock);
Let's not support SDHCI_QUIRK_CLOCK_BEFORE_RESET
> +
> + sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
> + host->pending_reset = false;
> + }
> +
> + host->mrqs_done[i] = NULL;
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + if (host->ops->request_done)
> + host->ops->request_done(host, mrq);
> + else
> + mmc_request_done(host->mmc, mrq);
> +
> + return false;
> +}
> +
> +static void sdhci_uhs2_complete_work(struct work_struct *work)
> +{
> + struct sdhci_host *host = container_of(work, struct sdhci_host,
> + complete_work);
> +
> + if (!sdhci_uhs2_mode(host)) {
> + sdhci_complete_work(work);
> + return;
> + }
> +
> + while (!sdhci_uhs2_request_done(host))
> + ;
> +}
> +
> +/*****************************************************************************\
> + * *
> + * Interrupt handling *
> + * *
> +\*****************************************************************************/
> +
> +static void __sdhci_uhs2_irq(struct sdhci_host *host, u32 uhs2mask)
> +{
> + struct mmc_command *cmd = host->cmd;
> +
> + DBG("*** %s got UHS2 error interrupt: 0x%08x\n",
> + mmc_hostname(host->mmc), uhs2mask);
> +
> + if (uhs2mask & SDHCI_UHS2_INT_CMD_ERR_MASK) {
> + if (!host->cmd) {
> + pr_err("%s: Got cmd interrupt 0x%08x but no cmd.\n",
> + mmc_hostname(host->mmc),
> + (unsigned int)uhs2mask);
> + sdhci_dumpregs(host);
> + return;
> + }
> + host->cmd->error = -EILSEQ;
> + if (uhs2mask & SDHCI_UHS2_INT_CMD_TIMEOUT)
> + host->cmd->error = -ETIMEDOUT;
> + }
> +
> + if (uhs2mask & SDHCI_UHS2_INT_DATA_ERR_MASK) {
> + if (!host->data) {
> + pr_err("%s: Got data interrupt 0x%08x but no data.\n",
> + mmc_hostname(host->mmc),
> + (unsigned int)uhs2mask);
> + sdhci_dumpregs(host);
> + return;
> + }
> +
> + if (uhs2mask & SDHCI_UHS2_INT_DEADLOCK_TIMEOUT) {
> + pr_err("%s: Got deadlock timeout interrupt 0x%08x\n",
> + mmc_hostname(host->mmc),
> + (unsigned int)uhs2mask);
> + host->data->error = -ETIMEDOUT;
> + } else if (uhs2mask & SDHCI_UHS2_INT_ADMA_ERROR) {
> + pr_err("%s: ADMA error = 0x %x\n",
> + mmc_hostname(host->mmc),
> + sdhci_readb(host, SDHCI_ADMA_ERROR));
> + host->data->error = -EIO;
> + } else {
> + host->data->error = -EILSEQ;
> + }
> + }
> +
> + if (host->data && host->data->error)
> + sdhci_uhs2_finish_data(host);
> + else
> + sdhci_finish_mrq(host, cmd->mrq);
> +}
> +
> +u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask)
> +{
> + u32 mask = intmask, uhs2mask;
> +
> + if (!(host->mmc->flags & MMC_UHS2_SUPPORT))
Please use sdhci_uhs2_mode()
> + goto out;
> +
> + if (intmask & SDHCI_INT_ERROR) {
> + uhs2mask = sdhci_readl(host, SDHCI_UHS2_INT_STATUS);
> + if (!(uhs2mask & SDHCI_UHS2_INT_ERROR_MASK))
> + goto cmd_irq;
> +
> + /* Clear error interrupts */
> + sdhci_writel(host, uhs2mask & SDHCI_UHS2_INT_ERROR_MASK,
> + SDHCI_UHS2_INT_STATUS);
> +
> + /* Handle error interrupts */
> + __sdhci_uhs2_irq(host, uhs2mask);
> +
> + /* Caller, shdci_irq(), doesn't have to care UHS-2 errors */
shdci -> sdhci
care -> care about
> + intmask &= ~SDHCI_INT_ERROR;
> + mask &= SDHCI_INT_ERROR;
> + }
> +
> +cmd_irq:
> + if (intmask & SDHCI_INT_CMD_MASK) {
> + /* Clear command interrupt */
> + sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK, SDHCI_INT_STATUS);
> +
> + /* Handle command interrupt */
> + if (intmask & SDHCI_INT_RESPONSE)
> + sdhci_uhs2_finish_command(host);
> +
> + /* Caller, shdci_irq(), doesn't have to care UHS-2 command */
shdci -> sdhci
care -> care about
command -> commands
> + intmask &= ~SDHCI_INT_CMD_MASK;
> + mask &= SDHCI_INT_CMD_MASK;
> + }
> +
> + /* Clear already-handled interrupts. */
> + sdhci_writel(host, mask, SDHCI_INT_STATUS);
> +
> +out:
> + return intmask;
> +}
> +EXPORT_SYMBOL_GPL(sdhci_uhs2_irq);
> +
> +static irqreturn_t sdhci_uhs2_thread_irq(int irq, void *dev_id)
> +{
> + struct sdhci_host *host = dev_id;
> + struct mmc_command *cmd;
> + unsigned long flags;
> + u32 isr;
> +
> + if (!sdhci_uhs2_mode(host))
> + return sdhci_thread_irq(irq, dev_id);
> +
> + while (!sdhci_uhs2_request_done(host))
> + ;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + isr = host->thread_isr;
> + host->thread_isr = 0;
> +
> + cmd = host->deferred_cmd;
> + if (cmd && !sdhci_uhs2_send_command_retry(host, cmd, flags))
> + sdhci_finish_mrq(host, cmd->mrq);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + if (isr & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
> + struct mmc_host *mmc = host->mmc;
> +
> + mmc->ops->card_event(mmc);
> + mmc_detect_change(mmc, msecs_to_jiffies(200));
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
> {
> struct sdhci_host *host = mmc_priv(mmc);
> @@ -978,7 +1199,7 @@ static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc)
> }
>
> /* Init complete, do soft reset and enable UHS2 error irqs. */
> - host->ops->uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
> + sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
> sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
> /*
> * N.B SDHCI_INT_ENABLE and SDHCI_SIGNAL_ENABLE was cleared
> diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
> index 184fee80253c..64b7f356c9fc 100644
> --- a/drivers/mmc/host/sdhci-uhs2.h
> +++ b/drivers/mmc/host/sdhci-uhs2.h
> @@ -179,5 +179,8 @@ bool sdhci_uhs2_mode(struct sdhci_host *host);
> void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
> void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
> void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
> +void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq);
> +int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq);
> +u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask);
>
> #endif /* __SDHCI_UHS2_H */
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index c547fc3b8b47..6c67b79102eb 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -47,6 +47,8 @@
> static unsigned int debug_quirks = 0;
> static unsigned int debug_quirks2;
>
> +static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
> +
> void sdhci_dumpregs(struct sdhci_host *host)
> {
> SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n");
> @@ -1256,11 +1258,12 @@ static int sdhci_external_dma_init(struct sdhci_host *host)
> return ret;
> }
>
> -static struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
> - struct mmc_data *data)
> +struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
> + struct mmc_data *data)
> {
> return data->flags & MMC_DATA_WRITE ? host->tx_chan : host->rx_chan;
> }
> +EXPORT_SYMBOL_GPL(sdhci_external_dma_channel);
Does not need export
>
> int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd)
> {
> @@ -1509,7 +1512,7 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
> sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
> }
>
> -static bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
> +bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
> {
> return (!(host->flags & SDHCI_DEVICE_DEAD) &&
> ((mrq->cmd && mrq->cmd->error) ||
> @@ -1517,8 +1520,9 @@ static bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
> (mrq->data && mrq->data->stop && mrq->data->stop->error) ||
> (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)));
> }
> +EXPORT_SYMBOL_GPL(sdhci_needs_reset);
>
> -static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
> +void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
> {
> int i;
>
> @@ -1538,6 +1542,7 @@ static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
>
> WARN_ON(i >= SDHCI_MAX_MRQS);
> }
> +EXPORT_SYMBOL_GPL(sdhci_set_mrq_done);
>
> void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> {
> @@ -3122,6 +3127,55 @@ static const struct mmc_host_ops sdhci_ops = {
> * *
> \*****************************************************************************/
>
> +void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq)
> +{
> + struct mmc_data *data = mrq->data;
> +
> + if (data && data->host_cookie == COOKIE_MAPPED) {
> + if (host->bounce_buffer) {
> + /*
> + * On reads, copy the bounced data into the
> + * sglist
> + */
> + if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
> + unsigned int length = data->bytes_xfered;
> +
> + if (length > host->bounce_buffer_size) {
> + pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
> + mmc_hostname(host->mmc),
> + host->bounce_buffer_size,
> + data->bytes_xfered);
> + /* Cap it down and continue */
> + length = host->bounce_buffer_size;
> + }
> + dma_sync_single_for_cpu(
> + host->mmc->parent,
> + host->bounce_addr,
> + host->bounce_buffer_size,
> + DMA_FROM_DEVICE);
> + sg_copy_from_buffer(data->sg,
> + data->sg_len,
> + host->bounce_buffer,
> + length);
> + } else {
> + /* No copying, just switch ownership */
> + dma_sync_single_for_cpu(
> + host->mmc->parent,
> + host->bounce_addr,
> + host->bounce_buffer_size,
> + mmc_get_dma_dir(data));
> + }
> + } else {
> + /* Unmap the raw data */
> + dma_unmap_sg(mmc_dev(host->mmc), data->sg,
> + data->sg_len,
> + mmc_get_dma_dir(data));
> + }
> + data->host_cookie = COOKIE_UNMAPPED;
> + }
> +}
> +EXPORT_SYMBOL_GPL(sdhci_request_done_dma);
> +
> static bool sdhci_request_done(struct sdhci_host *host)
> {
> unsigned long flags;
> @@ -3186,48 +3240,7 @@ static bool sdhci_request_done(struct sdhci_host *host)
> sdhci_set_mrq_done(host, mrq);
> }
>
> - if (data && data->host_cookie == COOKIE_MAPPED) {
> - if (host->bounce_buffer) {
> - /*
> - * On reads, copy the bounced data into the
> - * sglist
> - */
> - if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
> - unsigned int length = data->bytes_xfered;
> -
> - if (length > host->bounce_buffer_size) {
> - pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
> - mmc_hostname(host->mmc),
> - host->bounce_buffer_size,
> - data->bytes_xfered);
> - /* Cap it down and continue */
> - length = host->bounce_buffer_size;
> - }
> - dma_sync_single_for_cpu(
> - mmc_dev(host->mmc),
> - host->bounce_addr,
> - host->bounce_buffer_size,
> - DMA_FROM_DEVICE);
> - sg_copy_from_buffer(data->sg,
> - data->sg_len,
> - host->bounce_buffer,
> - length);
> - } else {
> - /* No copying, just switch ownership */
> - dma_sync_single_for_cpu(
> - mmc_dev(host->mmc),
> - host->bounce_addr,
> - host->bounce_buffer_size,
> - mmc_get_dma_dir(data));
> - }
> - } else {
> - /* Unmap the raw data */
> - dma_unmap_sg(mmc_dev(host->mmc), data->sg,
> - data->sg_len,
> - mmc_get_dma_dir(data));
> - }
> - data->host_cookie = COOKIE_UNMAPPED;
> - }
> + sdhci_request_done_dma(host, mrq);
> }
>
> host->mrqs_done[i] = NULL;
> @@ -3242,7 +3255,7 @@ static bool sdhci_request_done(struct sdhci_host *host)
> return false;
> }
>
> -static void sdhci_complete_work(struct work_struct *work)
> +void sdhci_complete_work(struct work_struct *work)
> {
> struct sdhci_host *host = container_of(work, struct sdhci_host,
> complete_work);
> @@ -3250,6 +3263,7 @@ static void sdhci_complete_work(struct work_struct *work)
> while (!sdhci_request_done(host))
> ;
> }
> +EXPORT_SYMBOL_GPL(sdhci_complete_work);
>
> static void sdhci_timeout_timer(struct timer_list *t)
> {
> @@ -3705,7 +3719,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
> return result;
> }
>
> -static irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
> +irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
> {
> struct sdhci_host *host = dev_id;
> struct mmc_command *cmd;
> @@ -3735,6 +3749,7 @@ static irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
>
> return IRQ_HANDLED;
> }
> +EXPORT_SYMBOL_GPL(sdhci_thread_irq);
>
> /*****************************************************************************\
> * *
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index 3b87fcae1294..4979af7cabca 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -840,7 +840,10 @@ int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd);
> void sdhci_external_dma_release(struct sdhci_host *host);
> void __sdhci_external_dma_prepare_data(struct sdhci_host *host, struct mmc_command *cmd);
> void sdhci_external_dma_pre_transfer(struct sdhci_host *host, struct mmc_command *cmd);
> +struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host, struct mmc_data *data);
Does not need export
> #endif
> +bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq);
> +void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq);
> void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> void __sdhci_finish_data_common(struct sdhci_host *host);
> @@ -870,6 +873,9 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
> int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> struct mmc_ios *ios);
> void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
> +void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq);
> +void sdhci_complete_work(struct work_struct *work);
> +irqreturn_t sdhci_thread_irq(int irq, void *dev_id);
> void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
> dma_addr_t addr, int len, unsigned int cmd);
>
Hi, Adrian
On Fri, Jan 6, 2023 at 5:28 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > This is a sdhci version of mmc's set_ios operation.
> > It covers both UHS-I and UHS-II.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/host/sdhci-uhs2.c | 96 +++++++++++++++++++++++++++++++++++
> > drivers/mmc/host/sdhci-uhs2.h | 1 +
> > drivers/mmc/host/sdhci.c | 53 +++++++++++--------
> > drivers/mmc/host/sdhci.h | 2 +
> > 4 files changed, 131 insertions(+), 21 deletions(-)
> >
> > diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> > index 00b1b69b49ea..3d52d35a91a5 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.c
> > +++ b/drivers/mmc/host/sdhci-uhs2.c
> > @@ -213,6 +213,68 @@ void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
> > }
> > EXPORT_SYMBOL_GPL(sdhci_uhs2_set_timeout);
> >
> > +/**
> > + * sdhci_uhs2_clear_set_irqs - set Error Interrupt Status Enable register
> > + * @host: SDHCI host
> > + * @clear: bit-wise clear mask
> > + * @set: bit-wise set mask
> > + *
> > + * Set/unset bits in UHS-II Error Interrupt Status Enable register
> > + */
> > +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
> > +{
> > + u32 ier;
> > +
> > + ier = sdhci_readl(host, SDHCI_UHS2_INT_STATUS_ENABLE);
> > + ier &= ~clear;
> > + ier |= set;
> > + sdhci_writel(host, ier, SDHCI_UHS2_INT_STATUS_ENABLE);
> > + sdhci_writel(host, ier, SDHCI_UHS2_INT_SIGNAL_ENABLE);
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_uhs2_clear_set_irqs);
> > +
> > +static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > +{
> > + struct sdhci_host *host = mmc_priv(mmc);
> > + u8 cmd_res, dead_lock;
> > + u16 ctrl_2;
> > +
> > + /* UHS2 Timeout Control */
> > + sdhci_calc_timeout_uhs2(host, &cmd_res, &dead_lock);
> > +
> > + /* change to use calculate value */
> > + cmd_res |= FIELD_PREP(SDHCI_UHS2_TIMER_CTRL_DEADLOCK_MASK, dead_lock);
> > +
> > + sdhci_uhs2_clear_set_irqs(host,
> > + SDHCI_UHS2_INT_CMD_TIMEOUT |
> > + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT,
> > + 0);
> > + sdhci_writeb(host, cmd_res, SDHCI_UHS2_TIMER_CTRL);
> > + sdhci_uhs2_clear_set_irqs(host, 0,
> > + SDHCI_UHS2_INT_CMD_TIMEOUT |
> > + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT);
> > +
> > + /* UHS2 timing */
>
> Please extend comment to include:
>
> Note, UHS2 timing is disabled when powering off
>
> > + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> > + if (ios->timing == MMC_TIMING_SD_UHS2)
> > + ctrl_2 |= SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE;
> > + else
> > + ctrl_2 &= ~(SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE);
> > + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> > +
> > + if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
> > + sdhci_enable_preset_value(host, true);
> > +
> > + if (host->ops->set_power)
> > + host->ops->set_power(host, ios->power_mode, ios->vdd);
> > + else
> > + sdhci_uhs2_set_power(host, ios->power_mode, ios->vdd);
>
> sdhci_set_ios_common() already set the power. Are both needed?
>
> > + udelay(100);
>
> Please add a comment for why this delay is here.
>
> > +
> > + host->timing = ios->timing;
>
> Please move this up to where the timing change is.
>
The ios->timing was changed in sd_uhs2_power_up function and not
modified in this function(__sdhci_uhs2_set_ios), therefore would you
want me to move this to the sd_uhs2_power_up function?
As I know the ios->timing is used in sdhci_set_clock function, setting
the host->timing before call the sdhci_set_clock function ensures that
the host->timing used in sdhci_set_clock function is the most recent
value. What do you think about this?
> > + sdhci_set_clock(host, host->clock);
> > +}
> > +
> > /*****************************************************************************\
> > * *
> > * MMC callbacks *
> > @@ -234,6 +296,39 @@ static int sdhci_uhs2_start_signal_voltage_switch(struct mmc_host *mmc,
> > return sdhci_start_signal_voltage_switch(mmc, ios);
> > }
> >
> > +int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>
> Should be static
>
> > +{
> > + struct sdhci_host *host = mmc_priv(mmc);
> > +
> > + if (!(host->version >= SDHCI_SPEC_400) ||
> > + !(host->mmc->flags & MMC_UHS2_SUPPORT &&
> > + host->mmc->caps2 & MMC_CAP2_SD_UHS2)) {
>
> This can be just:
>
> if (!sdhci_uhs2_mode(host)) {
>
> Not sure if this is actually possible?
>
> > + sdhci_set_ios(mmc, ios);
> > + return 0;
> > + }
> > +
> > + if (ios->power_mode == MMC_POWER_UNDEFINED)
> > + return 0;
> > +
> > + if (host->flags & SDHCI_DEVICE_DEAD) {
> > + if (!IS_ERR(mmc->supply.vmmc) &&
> > + ios->power_mode == MMC_POWER_OFF)
> > + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> > + if (!IS_ERR_OR_NULL(mmc->supply.vmmc2) &&
> > + ios->power_mode == MMC_POWER_OFF)
> > + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
>
> This can be just:
>
> if (ios->power_mode == MMC_POWER_OFF) {
> mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
> }
>
> > + return -1;
> > + }
> > +
> > + host->timing = ios->timing;
>
> __sdhci_uhs2_set_ios() does this so it is not needed here.
>
> > +
> > + sdhci_set_ios_common(mmc, ios);
> > +
> > + __sdhci_uhs2_set_ios(mmc, ios);
> > +
> > + return 0;
> > +}
> > +
> > /*****************************************************************************\
> > * *
> > * Driver init/exit *
> > @@ -244,6 +339,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> > {
> > host->mmc_host_ops.start_signal_voltage_switch =
> > sdhci_uhs2_start_signal_voltage_switch;
> > + host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
> >
> > return 0;
> > }
> > diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
> > index a58ef19c08aa..184fee80253c 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.h
> > +++ b/drivers/mmc/host/sdhci-uhs2.h
> > @@ -178,5 +178,6 @@ void sdhci_uhs2_dump_regs(struct sdhci_host *host);
> > bool sdhci_uhs2_mode(struct sdhci_host *host);
> > void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
> > void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
> > +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
> >
> > #endif /* __SDHCI_UHS2_H */
> > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> > index 99633a3ef549..49bbdc155b2b 100644
> > --- a/drivers/mmc/host/sdhci.c
> > +++ b/drivers/mmc/host/sdhci.c
> > @@ -47,8 +47,6 @@
> > static unsigned int debug_quirks = 0;
> > static unsigned int debug_quirks2;
> >
> > -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
> > -
> > static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
> >
> > void sdhci_dumpregs(struct sdhci_host *host)
> > @@ -1877,6 +1875,9 @@ static u16 sdhci_get_preset_value(struct sdhci_host *host)
> > case MMC_TIMING_MMC_HS400:
> > preset = sdhci_readw(host, SDHCI_PRESET_FOR_HS400);
> > break;
> > + case MMC_TIMING_SD_UHS2:
> > + preset = sdhci_readw(host, SDHCI_PRESET_FOR_UHS2);
> > + break;
> > default:
> > pr_warn("%s: Invalid UHS-I mode selected\n",
> > mmc_hostname(host->mmc));
> > @@ -2325,24 +2326,9 @@ static bool sdhci_presetable_values_change(struct sdhci_host *host, struct mmc_i
> > (sdhci_preset_needed(host, ios->timing) || host->drv_type != ios->drv_type);
> > }
> >
> > -void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios)
> > {
> > struct sdhci_host *host = mmc_priv(mmc);
> > - bool reinit_uhs = host->reinit_uhs;
> > - bool turning_on_clk = false;
> > - u8 ctrl;
> > -
> > - host->reinit_uhs = false;
> > -
> > - if (ios->power_mode == MMC_POWER_UNDEFINED)
> > - return;
> > -
> > - if (host->flags & SDHCI_DEVICE_DEAD) {
> > - if (!IS_ERR(mmc->supply.vmmc) &&
> > - ios->power_mode == MMC_POWER_OFF)
> > - mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> > - return;
> > - }
> >
> > /*
> > * Reset the chip on each power off.
> > @@ -2359,8 +2345,6 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > sdhci_enable_preset_value(host, false);
> >
> > if (!ios->clock || ios->clock != host->clock) {
> > - turning_on_clk = ios->clock && !host->clock;
> > -
> > host->ops->set_clock(host, ios->clock);
> > host->clock = ios->clock;
> >
> > @@ -2381,6 +2365,32 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > host->ops->set_power(host, ios->power_mode, ios->vdd);
> > else
> > sdhci_set_power(host, ios->power_mode, ios->vdd);
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_set_ios_common);
> > +
> > +void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > +{
> > + struct sdhci_host *host = mmc_priv(mmc);
> > + bool reinit_uhs = host->reinit_uhs;
> > + bool turning_on_clk = false;
> > + u8 ctrl;
> > +
> > + host->reinit_uhs = false;
> > +
> > + if (ios->power_mode == MMC_POWER_UNDEFINED)
> > + return;
> > +
> > + if (host->flags & SDHCI_DEVICE_DEAD) {
> > + if (!IS_ERR(mmc->supply.vmmc) &&
> > + ios->power_mode == MMC_POWER_OFF)
> > + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> > + return;
> > + }
> > +
> > + sdhci_set_ios_common(mmc, ios);
> > +
> > + if (!ios->clock || ios->clock != host->clock)
> > + turning_on_clk = ios->clock && !host->clock;
> >
> > if (host->ops->platform_send_init_74_clocks)
> > host->ops->platform_send_init_74_clocks(host, ios->power_mode);
> > @@ -2959,7 +2969,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
> > }
> > EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
> >
> > -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> > +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> > {
> > /* Host Controller v3.00 defines preset value registers */
> > if (host->version < SDHCI_SPEC_300)
> > @@ -2987,6 +2997,7 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> > host->preset_enabled = enable;
> > }
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_enable_preset_value);
> >
> > static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
> > int err)
> > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> > index df7fa0c0ebf8..c2f989dc2361 100644
> > --- a/drivers/mmc/host/sdhci.h
> > +++ b/drivers/mmc/host/sdhci.h
> > @@ -850,6 +850,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
> > void sdhci_reset(struct sdhci_host *host, u8 mask);
> > void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
> > int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
> > +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
> > +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios);
> > void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
> > int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> > struct mmc_ios *ios);
>
Thanks, Victor Shih
On 26/01/23 12:58, Victor Shih wrote:
> Hi, Adrian
>
> On Fri, Jan 6, 2023 at 5:28 AM Adrian Hunter <[email protected]> wrote:
>>
>> On 13/12/22 11:00, Victor Shih wrote:
>>> This is a sdhci version of mmc's set_ios operation.
>>> It covers both UHS-I and UHS-II.
>>>
>>> Signed-off-by: Ben Chuang <[email protected]>
>>> Signed-off-by: AKASHI Takahiro <[email protected]>
>>> Signed-off-by: Victor Shih <[email protected]>
>>> ---
>>> drivers/mmc/host/sdhci-uhs2.c | 96 +++++++++++++++++++++++++++++++++++
>>> drivers/mmc/host/sdhci-uhs2.h | 1 +
>>> drivers/mmc/host/sdhci.c | 53 +++++++++++--------
>>> drivers/mmc/host/sdhci.h | 2 +
>>> 4 files changed, 131 insertions(+), 21 deletions(-)
>>>
>>> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
>>> index 00b1b69b49ea..3d52d35a91a5 100644
>>> --- a/drivers/mmc/host/sdhci-uhs2.c
>>> +++ b/drivers/mmc/host/sdhci-uhs2.c
>>> @@ -213,6 +213,68 @@ void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
>>> }
>>> EXPORT_SYMBOL_GPL(sdhci_uhs2_set_timeout);
>>>
>>> +/**
>>> + * sdhci_uhs2_clear_set_irqs - set Error Interrupt Status Enable register
>>> + * @host: SDHCI host
>>> + * @clear: bit-wise clear mask
>>> + * @set: bit-wise set mask
>>> + *
>>> + * Set/unset bits in UHS-II Error Interrupt Status Enable register
>>> + */
>>> +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
>>> +{
>>> + u32 ier;
>>> +
>>> + ier = sdhci_readl(host, SDHCI_UHS2_INT_STATUS_ENABLE);
>>> + ier &= ~clear;
>>> + ier |= set;
>>> + sdhci_writel(host, ier, SDHCI_UHS2_INT_STATUS_ENABLE);
>>> + sdhci_writel(host, ier, SDHCI_UHS2_INT_SIGNAL_ENABLE);
>>> +}
>>> +EXPORT_SYMBOL_GPL(sdhci_uhs2_clear_set_irqs);
>>> +
>>> +static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>> +{
>>> + struct sdhci_host *host = mmc_priv(mmc);
>>> + u8 cmd_res, dead_lock;
>>> + u16 ctrl_2;
>>> +
>>> + /* UHS2 Timeout Control */
>>> + sdhci_calc_timeout_uhs2(host, &cmd_res, &dead_lock);
>>> +
>>> + /* change to use calculate value */
>>> + cmd_res |= FIELD_PREP(SDHCI_UHS2_TIMER_CTRL_DEADLOCK_MASK, dead_lock);
>>> +
>>> + sdhci_uhs2_clear_set_irqs(host,
>>> + SDHCI_UHS2_INT_CMD_TIMEOUT |
>>> + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT,
>>> + 0);
>>> + sdhci_writeb(host, cmd_res, SDHCI_UHS2_TIMER_CTRL);
>>> + sdhci_uhs2_clear_set_irqs(host, 0,
>>> + SDHCI_UHS2_INT_CMD_TIMEOUT |
>>> + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT);
>>> +
>>> + /* UHS2 timing */
>>
>> Please extend comment to include:
>>
>> Note, UHS2 timing is disabled when powering off
>>
>>> + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
>>> + if (ios->timing == MMC_TIMING_SD_UHS2)
>>> + ctrl_2 |= SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE;
>>> + else
>>> + ctrl_2 &= ~(SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE);
>>> + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
>>> +
>>> + if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
>>> + sdhci_enable_preset_value(host, true);
>>> +
>>> + if (host->ops->set_power)
>>> + host->ops->set_power(host, ios->power_mode, ios->vdd);
>>> + else
>>> + sdhci_uhs2_set_power(host, ios->power_mode, ios->vdd);
>>
>> sdhci_set_ios_common() already set the power. Are both needed?
>>
>>> + udelay(100);
>>
>> Please add a comment for why this delay is here.
>>
>>> +
>>> + host->timing = ios->timing;
>>
>> Please move this up to where the timing change is.
>>
>
> The ios->timing was changed in sd_uhs2_power_up function and not
> modified in this function(__sdhci_uhs2_set_ios), therefore would you
> want me to move this to the sd_uhs2_power_up function?
No
>
> As I know the ios->timing is used in sdhci_set_clock function, setting
> the host->timing before call the sdhci_set_clock function ensures that
> the host->timing used in sdhci_set_clock function is the most recent
> value. What do you think about this?
I just meant move it below the timing chunk i.e.
/* UHS2 timing. Note, UHS2 timing is disabled when powering off */
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
if (ios->timing == MMC_TIMING_SD_UHS2)
ctrl_2 |= SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE;
else
ctrl_2 &= ~(SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE);
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
host->timing = ios->timing;
>
>>> + sdhci_set_clock(host, host->clock);
>>> +}
>>> +
>>> /*****************************************************************************\
>>> * *
>>> * MMC callbacks *
>>> @@ -234,6 +296,39 @@ static int sdhci_uhs2_start_signal_voltage_switch(struct mmc_host *mmc,
>>> return sdhci_start_signal_voltage_switch(mmc, ios);
>>> }
>>>
>>> +int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>
>> Should be static
>>
>>> +{
>>> + struct sdhci_host *host = mmc_priv(mmc);
>>> +
>>> + if (!(host->version >= SDHCI_SPEC_400) ||
>>> + !(host->mmc->flags & MMC_UHS2_SUPPORT &&
>>> + host->mmc->caps2 & MMC_CAP2_SD_UHS2)) {
>>
>> This can be just:
>>
>> if (!sdhci_uhs2_mode(host)) {
>>
>> Not sure if this is actually possible?
>>
>>> + sdhci_set_ios(mmc, ios);
>>> + return 0;
>>> + }
>>> +
>>> + if (ios->power_mode == MMC_POWER_UNDEFINED)
>>> + return 0;
>>> +
>>> + if (host->flags & SDHCI_DEVICE_DEAD) {
>>> + if (!IS_ERR(mmc->supply.vmmc) &&
>>> + ios->power_mode == MMC_POWER_OFF)
>>> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
>>> + if (!IS_ERR_OR_NULL(mmc->supply.vmmc2) &&
>>> + ios->power_mode == MMC_POWER_OFF)
>>> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
>>
>> This can be just:
>>
>> if (ios->power_mode == MMC_POWER_OFF) {
>> mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
>> mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
>> }
>>
>>> + return -1;
>>> + }
>>> +
>>> + host->timing = ios->timing;
>>
>> __sdhci_uhs2_set_ios() does this so it is not needed here.
>>
>>> +
>>> + sdhci_set_ios_common(mmc, ios);
>>> +
>>> + __sdhci_uhs2_set_ios(mmc, ios);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> /*****************************************************************************\
>>> * *
>>> * Driver init/exit *
>>> @@ -244,6 +339,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
>>> {
>>> host->mmc_host_ops.start_signal_voltage_switch =
>>> sdhci_uhs2_start_signal_voltage_switch;
>>> + host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
>>>
>>> return 0;
>>> }
>>> diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
>>> index a58ef19c08aa..184fee80253c 100644
>>> --- a/drivers/mmc/host/sdhci-uhs2.h
>>> +++ b/drivers/mmc/host/sdhci-uhs2.h
>>> @@ -178,5 +178,6 @@ void sdhci_uhs2_dump_regs(struct sdhci_host *host);
>>> bool sdhci_uhs2_mode(struct sdhci_host *host);
>>> void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
>>> void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
>>> +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
>>>
>>> #endif /* __SDHCI_UHS2_H */
>>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>>> index 99633a3ef549..49bbdc155b2b 100644
>>> --- a/drivers/mmc/host/sdhci.c
>>> +++ b/drivers/mmc/host/sdhci.c
>>> @@ -47,8 +47,6 @@
>>> static unsigned int debug_quirks = 0;
>>> static unsigned int debug_quirks2;
>>>
>>> -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
>>> -
>>> static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
>>>
>>> void sdhci_dumpregs(struct sdhci_host *host)
>>> @@ -1877,6 +1875,9 @@ static u16 sdhci_get_preset_value(struct sdhci_host *host)
>>> case MMC_TIMING_MMC_HS400:
>>> preset = sdhci_readw(host, SDHCI_PRESET_FOR_HS400);
>>> break;
>>> + case MMC_TIMING_SD_UHS2:
>>> + preset = sdhci_readw(host, SDHCI_PRESET_FOR_UHS2);
>>> + break;
>>> default:
>>> pr_warn("%s: Invalid UHS-I mode selected\n",
>>> mmc_hostname(host->mmc));
>>> @@ -2325,24 +2326,9 @@ static bool sdhci_presetable_values_change(struct sdhci_host *host, struct mmc_i
>>> (sdhci_preset_needed(host, ios->timing) || host->drv_type != ios->drv_type);
>>> }
>>>
>>> -void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>> +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios)
>>> {
>>> struct sdhci_host *host = mmc_priv(mmc);
>>> - bool reinit_uhs = host->reinit_uhs;
>>> - bool turning_on_clk = false;
>>> - u8 ctrl;
>>> -
>>> - host->reinit_uhs = false;
>>> -
>>> - if (ios->power_mode == MMC_POWER_UNDEFINED)
>>> - return;
>>> -
>>> - if (host->flags & SDHCI_DEVICE_DEAD) {
>>> - if (!IS_ERR(mmc->supply.vmmc) &&
>>> - ios->power_mode == MMC_POWER_OFF)
>>> - mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
>>> - return;
>>> - }
>>>
>>> /*
>>> * Reset the chip on each power off.
>>> @@ -2359,8 +2345,6 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>> sdhci_enable_preset_value(host, false);
>>>
>>> if (!ios->clock || ios->clock != host->clock) {
>>> - turning_on_clk = ios->clock && !host->clock;
>>> -
>>> host->ops->set_clock(host, ios->clock);
>>> host->clock = ios->clock;
>>>
>>> @@ -2381,6 +2365,32 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>> host->ops->set_power(host, ios->power_mode, ios->vdd);
>>> else
>>> sdhci_set_power(host, ios->power_mode, ios->vdd);
>>> +}
>>> +EXPORT_SYMBOL_GPL(sdhci_set_ios_common);
>>> +
>>> +void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>> +{
>>> + struct sdhci_host *host = mmc_priv(mmc);
>>> + bool reinit_uhs = host->reinit_uhs;
>>> + bool turning_on_clk = false;
>>> + u8 ctrl;
>>> +
>>> + host->reinit_uhs = false;
>>> +
>>> + if (ios->power_mode == MMC_POWER_UNDEFINED)
>>> + return;
>>> +
>>> + if (host->flags & SDHCI_DEVICE_DEAD) {
>>> + if (!IS_ERR(mmc->supply.vmmc) &&
>>> + ios->power_mode == MMC_POWER_OFF)
>>> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
>>> + return;
>>> + }
>>> +
>>> + sdhci_set_ios_common(mmc, ios);
>>> +
>>> + if (!ios->clock || ios->clock != host->clock)
>>> + turning_on_clk = ios->clock && !host->clock;
>>>
>>> if (host->ops->platform_send_init_74_clocks)
>>> host->ops->platform_send_init_74_clocks(host, ios->power_mode);
>>> @@ -2959,7 +2969,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
>>> }
>>> EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
>>>
>>> -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
>>> +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
>>> {
>>> /* Host Controller v3.00 defines preset value registers */
>>> if (host->version < SDHCI_SPEC_300)
>>> @@ -2987,6 +2997,7 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
>>> host->preset_enabled = enable;
>>> }
>>> }
>>> +EXPORT_SYMBOL_GPL(sdhci_enable_preset_value);
>>>
>>> static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
>>> int err)
>>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
>>> index df7fa0c0ebf8..c2f989dc2361 100644
>>> --- a/drivers/mmc/host/sdhci.h
>>> +++ b/drivers/mmc/host/sdhci.h
>>> @@ -850,6 +850,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
>>> void sdhci_reset(struct sdhci_host *host, u8 mask);
>>> void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
>>> int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
>>> +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
>>> +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios);
>>> void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
>>> int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>>> struct mmc_ios *ios);
>>
>
> Thanks, Victor Shih
On Tue, 13 Dec 2022 at 10:02, Victor Shih <[email protected]> wrote:
>
> Changes are:
> * Disable GL9755 overcurrent interrupt when power on/off on UHS-II.
> * Enable the internal clock when do reset on UHS-II mode.
> * Set ZC to 0x0 for Sandisk cards and set ZC to 0xB for others.
> * Increase timeout value before detecting UHS-II interface.
> * Add vendor settings fro UHS-II mode.
>
> Signed-off-by: Ben Chuang <[email protected]>
> Signed-off-by: AKASHI Takahiro <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/host/Kconfig | 1 +
> drivers/mmc/host/sdhci-pci-gli.c | 309 ++++++++++++++++++++++++++++++-
> 2 files changed, 309 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 9c22370fb778..e68cdcb72cba 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -111,6 +111,7 @@ config MMC_SDHCI_PCI
> tristate "SDHCI support on PCI bus"
> depends on MMC_SDHCI && PCI
> select MMC_CQHCI
> + select MMC_SDHCI_UHS2
> select IOSF_MBI if X86
> select MMC_SDHCI_IO_ACCESSORS
> help
> diff --git a/drivers/mmc/host/sdhci-pci-gli.c b/drivers/mmc/host/sdhci-pci-gli.c
[...]
> +static void gl9755_post_attach_sd(struct sdhci_host *host)
> +{
> + struct pci_dev *pdev;
> + struct sdhci_pci_chip *chip;
> + struct sdhci_pci_slot *slot;
> + u32 serdes;
> +
> + slot = sdhci_priv(host);
> + chip = slot->chip;
> + pdev = chip->pdev;
> +
> + gl9755_wt_on(pdev);
> +
> + pci_read_config_dword(pdev, PCI_GLI_9755_SerDes, &serdes);
> + serdes &= ~PCI_GLI_9755_UHS2_SERDES_ZC1;
> + serdes &= ~PCI_GLI_9755_UHS2_SERDES_ZC2;
> + serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC1,
> + GLI_9755_UHS2_SERDES_ZC1_VALUE);
> +
> + /* the manfid of sandisk card is 0x3 */
> + if (host->mmc->card->cid.manfid == 0x3)
> + serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC2,
> + GLI_9755_UHS2_SERDES_ZC2_SANDISK);
> + else
> + serdes |= FIELD_PREP(PCI_GLI_9755_UHS2_SERDES_ZC2,
> + GLI_9755_UHS2_SERDES_ZC2_DEFAULT);
> +
> + pci_write_config_dword(pdev, PCI_GLI_9755_SerDes, serdes);
Can you please clarify what kind of configuration you are doing here -
and in particular why it's based upon the card's manfid?
[...]
Kind regards
Uffe
"On Tue, 13 Dec 2022 at 10:01, Victor Shih <[email protected]> wrote:
>
> Embed UHS-II access/control functionality into the MMC request
> processing flow.
>
> Signed-off-by: Ulf Hansson <[email protected]>
> Signed-off-by: Jason Lai <[email protected]>
> Signed-off-by: Victor Shih <[email protected]>
> ---
> drivers/mmc/core/block.c | 6 +-
> drivers/mmc/core/core.c | 20 +
> drivers/mmc/core/mmc_ops.c | 25 +-
> drivers/mmc/core/mmc_ops.h | 1 +
> drivers/mmc/core/sd.c | 11 +-
> drivers/mmc/core/sd.h | 3 +
> drivers/mmc/core/sd_ops.c | 13 +
> drivers/mmc/core/sd_ops.h | 3 +
> drivers/mmc/core/sd_uhs2.c | 1171 +++++++++++++++++++++++++++++++++++-
> 9 files changed, 1206 insertions(+), 47 deletions(-)
>
> diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
> index 20da7ed43e6d..d3e8ec43cdd5 100644
> --- a/drivers/mmc/core/block.c
> +++ b/drivers/mmc/core/block.c
> @@ -1596,6 +1596,9 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
> struct request *req = mmc_queue_req_to_req(mqrq);
> struct mmc_blk_data *md = mq->blkdata;
> bool do_rel_wr, do_data_tag;
> + bool do_multi;
> +
> + do_multi = (card->uhs2_state & MMC_UHS2_INITIALIZED) ? true : false;
>
> mmc_blk_data_prep(mq, mqrq, recovery_mode, &do_rel_wr, &do_data_tag);
>
> @@ -1606,7 +1609,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
> brq->cmd.arg <<= 9;
> brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
>
> - if (brq->data.blocks > 1 || do_rel_wr) {
> + if (brq->data.blocks > 1 || do_rel_wr || do_multi) {
This looks wrong to me. UHS2 can use single block read/writes too. Right?
> /* SPI multiblock writes terminate using a special
> * token, not a STOP_TRANSMISSION request.
> */
> @@ -1619,6 +1622,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
> brq->mrq.stop = NULL;
> readcmd = MMC_READ_SINGLE_BLOCK;
> writecmd = MMC_WRITE_BLOCK;
> + brq->cmd.uhs2_tmode0_flag = 1;
As "do_multi" is always set for UHS2, setting this flag here seems to
be wrong/redundant.
Anyway, if I understand correctly, the flag is intended to be used to
inform the host driver whether the so-called 2L_HD_mode (half-duplex
or full-duplex) should be used for the I/O request or not.
To fix the above behaviour, I suggest we try to move the entire
control of the flag into mmc_uhs2_prepare_cmd(). We want the flag to
be set for multi block read/writes (CMD18 and CMD25), but only if the
host and card supports the 2L_HD_mode too. According to my earlier
suggestions, we should be able to check that via the bits we set
earlier in the ios->timing.
Moreover, by making mmc_uhs2_prepare_cmd() responsible for setting the
flag, I think we can also move the definition of the flag into the
struct uhs2_command. While at it, I suggest we also rename the flag
into "tmode_half_duplex", to better describe its purpose, which also
means the interpretation of the flag becomes inverted.
> }
> brq->cmd.opcode = rq_data_dir(req) == READ ? readcmd : writecmd;
>
> diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
> index ad1a40446813..01f8216885ad 100644
> --- a/drivers/mmc/core/core.c
> +++ b/drivers/mmc/core/core.c
> @@ -334,6 +334,8 @@ static int mmc_mrq_prep(struct mmc_host *host, struct mmc_request *mrq)
>
> int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
> {
> + struct uhs2_command uhs2_cmd;
> + __be32 payload[4]; /* for maximum size */
> int err;
>
> init_completion(&mrq->cmd_completion);
> @@ -351,6 +353,14 @@ int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
> if (err)
> return err;
>
> + if (host->card) {
> + if (host->card->uhs2_state & MMC_UHS2_INITIALIZED) {
> + uhs2_cmd.payload = payload;
> + mrq->cmd->uhs2_cmd = &uhs2_cmd;
> + mmc_uhs2_prepare_cmd(host, mrq);
> + }
> + }
To avoid open coding, please move this into mmc_mrq_prep() or add a
new helper function for it.
> +
> led_trigger_event(host->led, LED_FULL);
> __mmc_start_request(host, mrq);
>
> @@ -430,6 +440,8 @@ EXPORT_SYMBOL(mmc_wait_for_req_done);
> */
> int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq)
> {
> + struct uhs2_command uhs2_cmd;
> + __be32 payload[4]; /* for maximum size */
> int err;
>
> /*
> @@ -450,6 +462,14 @@ int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq)
> if (err)
> goto out_err;
>
> + if (host->card) {
> + if (host->card->uhs2_state & MMC_UHS2_INITIALIZED) {
> + uhs2_cmd.payload = payload;
> + mrq->cmd->uhs2_cmd = &uhs2_cmd;
> + mmc_uhs2_prepare_cmd(host, mrq);
> + }
> + }
Ditto.
> +
> err = host->cqe_ops->cqe_request(host, mrq);
> if (err)
> goto out_err;
> diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
> index 81c55bfd6e0c..daa1f4ccd99a 100644
> --- a/drivers/mmc/core/mmc_ops.c
> +++ b/drivers/mmc/core/mmc_ops.c
> @@ -144,10 +144,24 @@ int mmc_set_dsr(struct mmc_host *host)
> return mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
> }
>
> +int __mmc_go_idle(struct mmc_host *host)
> +{
> + struct mmc_command cmd = {};
> + int err;
> +
> + cmd.opcode = MMC_GO_IDLE_STATE;
> + cmd.arg = 0;
> + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
> +
> + err = mmc_wait_for_cmd(host, &cmd, 0);
> + mmc_delay(1);
> +
> + return err;
> +}
> +
> int mmc_go_idle(struct mmc_host *host)
> {
> int err;
> - struct mmc_command cmd = {};
>
> /*
> * Non-SPI hosts need to prevent chipselect going active during
> @@ -163,13 +177,7 @@ int mmc_go_idle(struct mmc_host *host)
> mmc_delay(1);
> }
>
> - cmd.opcode = MMC_GO_IDLE_STATE;
> - cmd.arg = 0;
> - cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
> -
> - err = mmc_wait_for_cmd(host, &cmd, 0);
> -
> - mmc_delay(1);
> + err = __mmc_go_idle(host);
The above changes for mmc_go_idle() are pure refactorings, please move
these changes into a separate patch that precedes $subject patch.
This will help to simplify the review.
>
> if (!mmc_host_is_spi(host)) {
> mmc_set_chip_select(host, MMC_CS_DONTCARE);
> @@ -300,6 +308,7 @@ int mmc_send_adtc_data(struct mmc_card *card, struct mmc_host *host, u32 opcode,
> * not R1 plus a data block.
> */
> cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
> + cmd.uhs2_tmode0_flag = 1;
As I stated above, I think we can drop this and rather manage the flag
from mmc_uhs2_prepare_cmd() instead. From now on, I will stop to
comment more on the use of the uhs2_tmode0_flag, as the similar
comments applies to more places.
[...]
> diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h
> index 3ba7b3cf4652..29c802dec988 100644
> --- a/drivers/mmc/core/sd_ops.h
> +++ b/drivers/mmc/core/sd_ops.h
> @@ -11,6 +11,7 @@
> #include <linux/types.h>
>
> struct mmc_card;
> +struct mmc_command;
Looks like this should be "struct mmc_request;" instead.
> struct mmc_host;
>
> int mmc_app_set_bus_width(struct mmc_card *card, int width);
> @@ -19,10 +20,12 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr);
> int mmc_send_if_cond_pcie(struct mmc_host *host, u32 ocr);
> int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
> int mmc_app_send_scr(struct mmc_card *card);
> +int mmc_decode_scr(struct mmc_card *card);
> int mmc_sd_switch(struct mmc_card *card, int mode, int group,
> u8 value, u8 *resp);
> int mmc_app_sd_status(struct mmc_card *card, void *ssr);
> int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card);
> +void mmc_uhs2_prepare_cmd(struct mmc_host *host, struct mmc_request *mrq);
>
> #endif
>
> diff --git a/drivers/mmc/core/sd_uhs2.c b/drivers/mmc/core/sd_uhs2.c
> index 800957f74632..a79eb08ec540 100644
> --- a/drivers/mmc/core/sd_uhs2.c
> +++ b/drivers/mmc/core/sd_uhs2.c
> @@ -1,48 +1,125 @@
> // SPDX-License-Identifier: GPL-2.0-only
> /*
> * Copyright (C) 2021 Linaro Ltd
> - *
> * Author: Ulf Hansson <[email protected]>
> *
> + * Copyright (C) 2014 Intel Corp, All Rights Reserved.
> + * Author: Yi Sun <[email protected]>
> + *
> + * Copyright (C) 2020 Genesys Logic, Inc.
> + * Authors: Ben Chuang <[email protected]>
> + *
> + * Copyright (C) 2020 Linaro Limited
> + * Author: AKASHI Takahiro <[email protected]>
> + *
> + * Copyright (C) 2022 Genesys Logic, Inc.
> + * Authors: Jason Lai <[email protected]>
> + *
> * Support for SD UHS-II cards
> */
> #include <linux/err.h>
> +#include <linux/pm_runtime.h>
>
> #include <linux/mmc/host.h>
> #include <linux/mmc/card.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sd.h>
> +#include <linux/mmc/sd_uhs2.h>
>
> +#include "card.h"
> #include "core.h"
> #include "bus.h"
> #include "sd.h"
> +#include "sd_ops.h"
> #include "mmc_ops.h"
>
> +#define UHS2_WAIT_CFG_COMPLETE_PERIOD_US (1 * 1000) /* 1ms */
> +#define UHS2_WAIT_CFG_COMPLETE_TIMEOUT_MS 100 /* 100ms */
> +
> static const unsigned int sd_uhs2_freqs[] = { 52000000, 26000000 };
> +int sd_uhs2_reinit(struct mmc_host *host);
This should be static - and please don't use forward declaration,
unless it's really necessary.
>
> -static int sd_uhs2_set_ios(struct mmc_host *host)
> +/*
> + * Internal function that does the actual ios call to the host driver,
> + * optionally printing some debug output.
> + */
> +static inline int sd_uhs2_set_ios(struct mmc_host *host)
> {
> struct mmc_ios *ios = &host->ios;
>
> + pr_debug("%s: clock %uHz powermode %u Vdd %u timing %u\n",
> + mmc_hostname(host), ios->clock, ios->power_mode, ios->vdd, ios->timing);
> +
> return host->ops->uhs2_set_ios(host, ios);
I have made some more thinking around the uhs2 host callbacks.
As we are using the ->uhs2_control() callback for all the other uhs2
operations, it seems silly to have a separate callback for the ios. In
other words, I suggest that we drop the ->uhs2_set_ios() callback
altogether and replace the two users of the above function with direct
calls to the ->uhs2_control() callback. See more below.
> }
>
> static int sd_uhs2_power_up(struct mmc_host *host)
> {
> + int err;
> +
> + if (host->ios.power_mode == MMC_POWER_ON)
> + return 0;
> +
> host->ios.vdd = fls(host->ocr_avail) - 1;
> host->ios.clock = host->f_init;
> host->ios.timing = MMC_TIMING_SD_UHS2;
> - host->ios.power_mode = MMC_POWER_UP;
> + host->ios.power_mode = MMC_POWER_ON;
>
> - return sd_uhs2_set_ios(host);
> + err = sd_uhs2_set_ios(host);
As stated above, I suggest we replace the above call with:
err = host->ops->uhs2_control(host, UHS2_SET_IOS);
> +
> + mmc_delay(host->uhs2_ios.power_delay_ms);
> +
> + return err;
> }
>
> -static void sd_uhs2_power_off(struct mmc_host *host)
> +static int sd_uhs2_power_off(struct mmc_host *host)
> {
> + if (host->ios.power_mode == MMC_POWER_OFF)
> + return 0;
> +
> host->ios.vdd = 0;
> host->ios.clock = 0;
> host->ios.timing = MMC_TIMING_LEGACY;
> host->ios.power_mode = MMC_POWER_OFF;
>
> - sd_uhs2_set_ios(host);
> + return sd_uhs2_set_ios(host);
Similar to the above, I suggest we replace the above call with:
return host->ops->uhs2_control(host, UHS2_SET_IOS);
>
> /*
> @@ -61,6 +146,88 @@ static int sd_uhs2_phy_init(struct mmc_host *host)
> */
> static int sd_uhs2_dev_init(struct mmc_host *host)
> {
> + struct mmc_command cmd = {0};
> + struct uhs2_command uhs2_cmd = {};
> + u32 cnt;
> + u32 dap, gap, resp_gap;
> + u16 header, arg;
> + __be32 payload[UHS2_DEV_INIT_PAYLOAD_LEN];
> + u8 gd = 0;
> + u8 resp[UHS2_DEV_ENUM_RESP_LEN] = {0};
> + int err;
> +
> + dap = host->uhs2_caps.dap;
> + gap = host->uhs2_caps.gap;
> +
> + /*
> + * Refer to UHS-II Addendum Version 1.02 Figure 6-21 to see DEVICE_INIT CCMD format.
> + * Head:
> + * - Control Write(R/W=1) with 4-Byte payload(PLEN=01b).
> + * - IOADR = CMD_BASE + 002h
> + * Payload:
> + * - bit [3:0] : GAP(Group Allocated Power)
> + * - bit [7:4] : GD(Group Descriptor)
> + * - bit [11] : Complete Flag
> + * - bit [15:12]: DAP(Device Allocated Power)
> + */
> + header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD;
> + arg = ((UHS2_DEV_CMD_DEVICE_INIT & 0xFF) << 8) |
> + UHS2_NATIVE_CMD_WRITE |
> + UHS2_NATIVE_CMD_PLEN_4B |
> + (UHS2_DEV_CMD_DEVICE_INIT >> 8);
> +
> + /*
> + * Refer to UHS-II Addendum Version 1.02 section 6.3.1.
> + * Max. time from DEVICE_INIT CCMD EOP reception on Device
> + * Rx to its SOP transmission on Device Tx(Tfwd_init_cmd) is
> + * 1 second.
> + */
> + cmd.busy_timeout = 1000;
> +
> + /*
> + * Refer to UHS-II Addendum Version 1.02 section 6.2.6.3.
> + * When the number of the DEVICE_INIT commands is reach to
> + * 30 tiems, Host shall stop issuing DEVICE_INIT command
> + * and regard it as an error.
> + */
> + for (cnt = 0; cnt < 30; cnt++) {
> + payload[0] = ((dap & 0xF) << 12) |
> + UHS2_DEV_INIT_COMPLETE_FLAG |
> + ((gd & 0xF) << 4) |
> + (gap & 0xF);
> +
> + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg,
> + payload, UHS2_DEV_INIT_PAYLOAD_LEN,
> + resp, UHS2_DEV_INIT_RESP_LEN);
> +
> + err = mmc_wait_for_cmd(host, &cmd, 0);
> +
Nitpick: Unnecessary line break.
> + if (err) {
> + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + if (resp[3] != (UHS2_DEV_CMD_DEVICE_INIT & 0xFF)) {
> + pr_err("%s: DEVICE_INIT response is wrong!\n",
> + mmc_hostname(host));
> + return -EIO;
> + }
> +
> + if (resp[5] & 0x8) {
> + host->uhs2_caps.group_desc = gd;
> + return 0;
> + }
> + resp_gap = resp[4] & 0x0F;
> + if (gap == resp_gap)
> + gd++;
> + }
> + if (cnt == 30) {
There is no need to check cnt here. It's always 30 if we reach this point.
> + pr_err("%s: DEVICE_INIT fail, already 30 times!\n",
> + mmc_hostname(host));
> + return -EIO;
> + }
> +
> return 0;
> }
[...]
> static int sd_uhs2_config_write(struct mmc_host *host, struct mmc_card *card)
> {
> + struct mmc_command cmd = {0};
> + struct uhs2_command uhs2_cmd = {};
> + u16 header, arg;
> + __be32 payload[UHS2_CFG_WRITE_PAYLOAD_LEN];
> + u8 nMinDataGap;
> + int err;
> + u8 resp[5] = {0};
> +
> + /*
> + * Use Control Write CCMD to set Generic Setting in Configuration Register.
> + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> + * - IOADR = Generic Setting Register(CFG_BASE + 008h)
> + * - Payload = New contents to be written to Generic Setting Register
> + */
> + header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | card->uhs2_config.node_id;
> + arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
> + UHS2_NATIVE_CMD_WRITE |
> + UHS2_NATIVE_CMD_PLEN_8B |
> + (UHS2_DEV_CONFIG_GEN_SET >> 8);
> +
> + if (card->uhs2_config.n_lanes == UHS2_DEV_CONFIG_2L_HD_FD &&
> + host->uhs2_caps.n_lanes == UHS2_DEV_CONFIG_2L_HD_FD) {
> + /* Support HD */
> + host->uhs2_ios.is_2L_HD_mode = true;
> + nMinDataGap = 1;
> + } else {
> + /* Only support 2L-FD so far */
> + host->uhs2_ios.is_2L_HD_mode = false;
> + nMinDataGap = 3;
> + }
> +
> + /*
> + * Most UHS-II cards only support FD and 2L-HD mode. Other lane numbers
> + * defined in UHS-II addendem Ver1.01 are optional.
> + */
> + host->uhs2_caps.n_lanes_set = UHS2_DEV_CONFIG_GEN_SET_2L_FD_HD;
> + card->uhs2_config.n_lanes_set = UHS2_DEV_CONFIG_GEN_SET_2L_FD_HD;
> +
> + payload[0] = card->uhs2_config.n_lanes_set << UHS2_DEV_CONFIG_N_LANES_POS;
> + payload[1] = 0;
> + payload[0] = cpu_to_be32(payload[0]);
> + payload[1] = cpu_to_be32(payload[1]);
> +
> + /*
> + * There is no payload because per spec, there should be
> + * no payload field for read CCMD.
> + * Plen is set in arg. Per spec, plen for read CCMD
> + * represents the len of read data which is assigned in payload
> + * of following RES (p136).
> + */
> + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> + NULL, 0);
> +
> + err = mmc_wait_for_cmd(host, &cmd, 0);
> + if (err) {
> + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + /*
> + * Use Control Write CCMD to set PHY Setting in Configuration Register.
> + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> + * - IOADR = PHY Setting Register(CFG_BASE + 00Ah)
> + * - Payload = New contents to be written to PHY Setting Register
> + */
> + arg = ((UHS2_DEV_CONFIG_PHY_SET & 0xFF) << 8) |
> + UHS2_NATIVE_CMD_WRITE |
> + UHS2_NATIVE_CMD_PLEN_8B |
> + (UHS2_DEV_CONFIG_PHY_SET >> 8);
> +
> + if (host->uhs2_caps.speed_range == UHS2_DEV_CONFIG_PHY_SET_SPEED_B) {
> + card->uhs2_state |= MMC_UHS2_SPEED_B;
As I suggested earlier, let's set a corresponding bit in ios->timing instead.
> + card->uhs2_config.speed_range_set =
> + UHS2_DEV_CONFIG_PHY_SET_SPEED_B;
> + } else {
> + card->uhs2_config.speed_range_set = UHS2_DEV_CONFIG_PHY_SET_SPEED_A;
> + card->uhs2_state &= ~MMC_UHS2_SPEED_B;
As I suggested earlier, I think it should be sufficient to clear this
in sd_uhs2_power_off() (by resetting ios->timing).
> + }
> +
> + payload[0] = card->uhs2_config.speed_range_set << UHS2_DEV_CONFIG_PHY_SET_SPEED_POS;
> +
> + card->uhs2_config.n_lss_sync_set = (max(card->uhs2_config.n_lss_sync,
> + host->uhs2_caps.n_lss_sync) >> 2) &
> + UHS2_DEV_CONFIG_N_LSS_SYN_MASK;
> + host->uhs2_caps.n_lss_sync_set = card->uhs2_config.n_lss_sync_set;
> +
> + card->uhs2_config.n_lss_dir_set = (max(card->uhs2_config.n_lss_dir,
> + host->uhs2_caps.n_lss_dir) >> 3) &
> + UHS2_DEV_CONFIG_N_LSS_DIR_MASK;
> + host->uhs2_caps.n_lss_dir_set = card->uhs2_config.n_lss_dir_set;
> +
> + payload[1] = (card->uhs2_config.n_lss_dir_set << UHS2_DEV_CONFIG_N_LSS_DIR_POS) |
> + card->uhs2_config.n_lss_sync_set;
> + payload[0] = cpu_to_be32(payload[0]);
> + payload[1] = cpu_to_be32(payload[1]);
> +
> + memset(resp, 0, sizeof(resp));
> +
> + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> + resp, UHS2_CFG_WRITE_PHY_SET_RESP_LEN);
> +
> + err = mmc_wait_for_cmd(host, &cmd, 0);
> + if (err) {
> + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + if ((resp[2] & 0x80)) {
> + pr_err("%s: %s: UHS2 CMD not accepted, resp= 0x%x!\n",
> + mmc_hostname(host), __func__, resp[2]);
> + return -EIO;
> + }
> +
> + /*
> + * Use Control Write CCMD to set LINK/TRAN Setting in Configuration Register.
> + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> + * - IOADR = LINK/TRAN Setting Register(CFG_BASE + 00Ch)
> + * - Payload = New contents to be written to LINK/TRAN Setting Register
> + */
> + arg = ((UHS2_DEV_CONFIG_LINK_TRAN_SET & 0xFF) << 8) |
> + UHS2_NATIVE_CMD_WRITE |
> + UHS2_NATIVE_CMD_PLEN_8B |
> + (UHS2_DEV_CONFIG_LINK_TRAN_SET >> 8);
> +
> + if (card->uhs2_config.app_type == UHS2_DEV_CONFIG_APP_SD_MEM)
> + card->uhs2_config.maxblk_len_set = UHS2_DEV_CONFIG_LT_SET_MAX_BLK_LEN;
> + else
> + card->uhs2_config.maxblk_len_set = min(card->uhs2_config.maxblk_len,
> + host->uhs2_caps.maxblk_len);
> + host->uhs2_caps.maxblk_len_set = card->uhs2_config.maxblk_len_set;
> +
> + card->uhs2_config.n_fcu_set = min(card->uhs2_config.n_fcu, host->uhs2_caps.n_fcu);
> + host->uhs2_caps.n_fcu_set = card->uhs2_config.n_fcu_set;
> +
> + card->uhs2_config.n_data_gap_set = max(nMinDataGap, card->uhs2_config.n_data_gap);
> + host->uhs2_caps.n_data_gap_set = card->uhs2_config.n_data_gap_set;
> +
> + host->uhs2_caps.max_retry_set = 3;
> + card->uhs2_config.max_retry_set = host->uhs2_caps.max_retry_set;
> +
> + payload[0] = (card->uhs2_config.maxblk_len_set << UHS2_DEV_CONFIG_MAX_BLK_LEN_POS) |
> + (card->uhs2_config.max_retry_set << UHS2_DEV_CONFIG_LT_SET_MAX_RETRY_POS) |
> + (card->uhs2_config.n_fcu_set << UHS2_DEV_CONFIG_N_FCU_POS);
> + payload[1] = card->uhs2_config.n_data_gap_set;
> + payload[0] = cpu_to_be32(payload[0]);
> + payload[1] = cpu_to_be32(payload[1]);
> +
> + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> + NULL, 0);
> +
> + err = mmc_wait_for_cmd(host, &cmd, 0);
> + if (err) {
> + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + /*
> + * Use Control Write CCMD to set Config Completion(payload bit 63) in Generic Setting
> + * Register.
> + * Header:
> + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> + * - IOADR = PGeneric Setting Register(CFG_BASE + 008h)
> + * Payload:
> + * - bit [63]: Config Completion
> + *
> + * DLSM transits to Active state immediately when Config Completion is set to 1.
> + */
> + arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
> + UHS2_NATIVE_CMD_WRITE |
> + UHS2_NATIVE_CMD_PLEN_8B |
> + (UHS2_DEV_CONFIG_GEN_SET >> 8);
> +
> + payload[0] = 0;
> + payload[1] = UHS2_DEV_CONFIG_GEN_SET_CFG_COMPLETE;
> + payload[0] = cpu_to_be32(payload[0]);
> + payload[1] = cpu_to_be32(payload[1]);
> +
> + memset(resp, 0, sizeof(resp));
> + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> + resp, UHS2_CFG_WRITE_GENERIC_SET_RESP_LEN);
> +
> + err = mmc_wait_for_cmd(host, &cmd, 0);
> + if (err) {
> + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + /* Set host Config Setting registers */
> + err = host->ops->uhs2_control(host, UHS2_SET_CONFIG);
> + if (err) {
> + pr_err("%s: %s: UHS2 SET_CONFIG fail!\n", mmc_hostname(host), __func__);
> + return err;
> + }
> +
> + return 0;
> +}
[...]
> +static int sd_uhs2_change_speed(struct mmc_host *host, u32 node_id)
> +{
> + struct mmc_command cmd = {0};
> + struct uhs2_command uhs2_cmd = {};
> + u16 header, arg;
> + int err;
> + struct sd_uhs2_wait_active_state_data cb_data = {
> + .host = host,
> + .cmd = &cmd
> + };
> +
> + /* Change Speed Range at controller side. */
> + err = host->ops->uhs2_control(host, UHS2_SET_SPEED_B);
As I asked for in patch5, is this call really needed for sdhci?
> + if (err) {
> + pr_err("%s: %s: UHS2 SET_SPEED fail!\n", mmc_hostname(host), __func__);
> + return err;
> + }
> +
> + err = sd_uhs2_go_dormant(host, node_id);
> + if (err) {
> + pr_err("%s: %s: UHS2 GO_DORMANT_STATE fail, err= 0x%x!\n",
> + mmc_hostname(host), __func__, err);
> + return err;
> + }
> +
> + /*
> + * Use Control Read CCMD to check Config Completion(bit 63) in Generic Setting Register.
> + * - Control Read(R/W=0) with 8-Byte payload(PLEN=10b).
> + * - IOADR = Generic Setting Register(CFG_BASE + 008h)
> + *
> + * When UHS-II card been switched to new speed mode, it will set Config Completion to 1.
> + */
> + header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | node_id;
> + arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
> + UHS2_NATIVE_CMD_READ |
> + UHS2_NATIVE_CMD_PLEN_8B |
> + (UHS2_DEV_CONFIG_GEN_SET >> 8);
> +
> + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, NULL, 0, NULL, 0);
> + err = __mmc_poll_for_busy(host, UHS2_WAIT_CFG_COMPLETE_PERIOD_US,
> + UHS2_WAIT_CFG_COMPLETE_TIMEOUT_MS,
> + &__sd_uhs2_wait_active_state_cb, &cb_data);
> + if (err) {
> + pr_err("%s: %s: Not switch to Active in 100 ms\n", mmc_hostname(host), __func__);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int sd_uhs2_get_ro(struct mmc_host *host)
> +{
> + /*
> + * Some systems don't feature a write-protect pin and don't need one.
> + * E.g. because they only have micro-SD card slot. For those systems
> + * assume that the SD card is always read-write.
> + */
> + if (host->caps2 & MMC_CAP2_NO_WRITE_PROTECT)
> + return 0;
> +
> + if (!host->ops->get_ro)
> + return -1;
> +
> + return host->ops->get_ro(host);
Let's avoid the open coding and use mmc_sd_get_ro() instead.
> +}
> +
> +/*
> + * Mask off any voltages we don't support and select
> + * the lowest voltage
> + */
> +u32 sd_uhs2_select_voltage(struct mmc_host *host, u32 ocr)
> +{
> + int bit;
> + int err;
> +
> + /*
> + * Sanity check the voltages that the card claims to
> + * support.
> + */
> + if (ocr & 0x7F) {
> + dev_warn(mmc_dev(host), "card claims to support voltages below defined range\n");
> + ocr &= ~0x7F;
> + }
> +
> + ocr &= host->ocr_avail;
> + if (!ocr) {
> + dev_warn(mmc_dev(host), "no support for card's volts\n");
> + return 0;
> + }
> +
> + if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) {
> + bit = ffs(ocr) - 1;
> + ocr &= 3 << bit;
> + /* Power cycle */
> + err = sd_uhs2_power_off(host);
> + if (err)
> + return 0;
> + err = sd_uhs2_reinit(host);
As also pointed out by Adrian, this path becomes circular and doesn't
work as expected.
At this point, I would prefer to keep things as simple as possible, so
I suggest that we drop this path entirely for UHS2.
> + if (err)
> + return 0;
> + } else {
> + bit = fls(ocr) - 1;
> + ocr &= 3 << bit;
Note that, in mmc_select_voltage() the corresponding code has been
updated in commit 39a72dbfe188 ("mmc: core: properly select voltage
range without power cycle") that fixes a real problem for us.
By taking the above observations into consideration, it looks like it
may be better to reuse mmc_select_voltage() for UHS-II after all. To
prevent the power cycle path for UHS-II, we can just add a check for
the ios->timings in there. That should work, I think.
> + if (bit != host->ios.vdd)
> + dev_warn(mmc_dev(host), "exceeding card's volts\n");
> + }
> +
> + return ocr;
> +}
> +
> /*
> * Initialize the UHS-II card through the SD-TRAN transport layer. This enables
> * commands/requests to be backwards compatible through the legacy SD protocol.
> @@ -107,14 +893,149 @@ static int sd_uhs2_config_write(struct mmc_host *host, struct mmc_card *card)
> */
> static int sd_uhs2_legacy_init(struct mmc_host *host, struct mmc_card *card)
I decided to postpone the review of the following parts below, which
consists of the legacy card initialization (SD-tran) and the
power-management support - as it looks like there are enough for you
to work on for a while.
Let me also ask about the power management support - did you test the
system suspend/resume path? Just to understand, if this is more an
attempt to make it work or whether it's actually tested/works?
[...]
Kind regards
Uffe
Hi, Adrian
On Fri, Jan 6, 2023 at 5:27 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > This is a UHS-II version of sdhci's set_power operation.
> > VDD2, as well as VDD, is handled here.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/host/sdhci-uhs2.c | 46 +++++++++++++++++++++++++
> > drivers/mmc/host/sdhci.c | 63 +++++++++++++++++++----------------
> > drivers/mmc/host/sdhci.h | 1 +
> > 3 files changed, 82 insertions(+), 28 deletions(-)
> >
> > diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> > index ae862e1eadab..780491781613 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.c
> > +++ b/drivers/mmc/host/sdhci-uhs2.c
> > @@ -57,6 +57,13 @@ EXPORT_SYMBOL_GPL(sdhci_uhs2_dump_regs);
> > * *
> > \*****************************************************************************/
> >
> > +static inline int mmc_opt_regulator_set_ocr(struct mmc_host *mmc,
> > + struct regulator *supply,
> > + unsigned short vdd_bit)
> > +{
> > + return IS_ERR_OR_NULL(supply) ? 0 : mmc_regulator_set_ocr(mmc, supply, vdd_bit);
> > +}
> > +
> > bool sdhci_uhs2_mode(struct sdhci_host *host)
> > {
> > return host->mmc->flags & MMC_UHS2_SUPPORT;
> > @@ -95,6 +102,45 @@ void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask)
> > }
> > EXPORT_SYMBOL_GPL(sdhci_uhs2_reset);
> >
> > +static void sdhci_uhs2_set_power(struct sdhci_host *host, unsigned char mode,
> > + unsigned short vdd)
> > +{
> > + struct mmc_host *mmc = host->mmc;
> > + u8 pwr;
> > +
> > + if (mode != MMC_POWER_OFF) {
> > + pwr = sdhci_get_vdd_value(vdd);
> > + if (!pwr)
> > + WARN(1, "%s: Invalid vdd %#x\n",
> > + mmc_hostname(host->mmc), vdd);
> > + pwr |= SDHCI_VDD2_POWER_180;
> > + }
> > +
> > + if (host->pwr == pwr)
> > + return;
> > + host->pwr = pwr;
> > +
> > + if (pwr == 0) {
> > + sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
> > +
> > + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> > + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
> > + } else {
> > + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
> > + /* support 1.8v only for now */
> > + mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, fls(MMC_VDD2_165_195) - 1);
> > +
> /* Clear the power reg before setting a new value */
> sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
>
I will update it in V7 version.
> > + /* vdd first */
> > + pwr |= SDHCI_POWER_ON;
> > + sdhci_writeb(host, pwr & 0xf, SDHCI_POWER_CONTROL);
> > + mdelay(5);
> > +
> > + pwr |= SDHCI_VDD2_POWER_ON;
> > + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
> > + mdelay(5);
> > + }
> > +}
> > +
> > /*****************************************************************************\
> > * *
> > * Driver init/exit *
> > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> > index 9b66b9a32c72..99633a3ef549 100644
> > --- a/drivers/mmc/host/sdhci.c
> > +++ b/drivers/mmc/host/sdhci.c
> > @@ -23,7 +23,7 @@
> > #include <linux/regulator/consumer.h>
> > #include <linux/pm_runtime.h>
> > #include <linux/of.h>
> > -
> > +#include <linux/bug.h>
> > #include <linux/leds.h>
> >
> > #include <linux/mmc/mmc.h>
> > @@ -2061,41 +2061,48 @@ static void sdhci_set_power_reg(struct sdhci_host *host, unsigned char mode,
> > sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
> > }
> >
> > +unsigned short sdhci_get_vdd_value(unsigned short vdd)
> > +{
> > + u8 pwr;
>
> It is simpler without pwr
>
I will update it in V7 version.
> > +
> > + switch (1 << vdd) {
> > + case MMC_VDD_165_195:
> > + /*
> > + * Without a regulator, SDHCI does not support 2.0v
> > + * so we only get here if the driver deliberately
> > + * added the 2.0v range to ocr_avail. Map it to 1.8v
> > + * for the purpose of turning on the power.
> > + */
> > + case MMC_VDD_20_21:
> > + pwr = SDHCI_POWER_180;
> > + break;
>
> return SDHCI_POWER_180;
>
I will update it in V7 version.
> > + case MMC_VDD_29_30:
> > + case MMC_VDD_30_31:
> > + pwr = SDHCI_POWER_300;
> > + break;
>
> return SDHCI_POWER_300;
>
I will update it in V7 version.
> > + case MMC_VDD_32_33:
> > + case MMC_VDD_33_34:
>
> Missing:
>
> /*
> * 3.4 ~ 3.6V are valid only for those platforms where it's
> * known that the voltage range is supported by hardware.
> */
> case MMC_VDD_34_35:
> case MMC_VDD_35_36:
>
>
I will update it in V7 version.
> > + pwr = SDHCI_POWER_330;
> > + break;
>
> return SDHCI_POWER_330;
>
I will update it in V7 version.
> > + default:
> > + pwr = 0;
> > + break;
>
> return 0;
>
I will update it in V7 version.
> > + }
> > +
> > + return pwr;
>
> Not needed. Control never reaches it anymore.
>
I will update it in V7 version.
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_get_vdd_value);
> > +
> > void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
> > unsigned short vdd)
> > {
> > u8 pwr = 0;
> >
> > if (mode != MMC_POWER_OFF) {
> > - switch (1 << vdd) {
> > - case MMC_VDD_165_195:
> > - /*
> > - * Without a regulator, SDHCI does not support 2.0v
> > - * so we only get here if the driver deliberately
> > - * added the 2.0v range to ocr_avail. Map it to 1.8v
> > - * for the purpose of turning on the power.
> > - */
> > - case MMC_VDD_20_21:
> > - pwr = SDHCI_POWER_180;
> > - break;
> > - case MMC_VDD_29_30:
> > - case MMC_VDD_30_31:
> > - pwr = SDHCI_POWER_300;
> > - break;
> > - case MMC_VDD_32_33:
> > - case MMC_VDD_33_34:
> > - /*
> > - * 3.4 ~ 3.6V are valid only for those platforms where it's
> > - * known that the voltage range is supported by hardware.
> > - */
> > - case MMC_VDD_34_35:
> > - case MMC_VDD_35_36:
> > - pwr = SDHCI_POWER_330;
> > - break;
> > - default:
> > + pwr = sdhci_get_vdd_value(vdd);
> > + if (!pwr) {
> > WARN(1, "%s: Invalid vdd %#x\n",
> > mmc_hostname(host->mmc), vdd);
> > - break;
> > }
> > }
> >
> > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> > index f41c19c76994..df7fa0c0ebf8 100644
> > --- a/drivers/mmc/host/sdhci.h
> > +++ b/drivers/mmc/host/sdhci.h
> > @@ -840,6 +840,7 @@ void sdhci_set_power(struct sdhci_host *host, unsigned char mode,
> > void sdhci_set_power_and_bus_voltage(struct sdhci_host *host,
> > unsigned char mode,
> > unsigned short vdd);
> > +unsigned short sdhci_get_vdd_value(unsigned short vdd);
> > void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
> > unsigned short vdd);
> > int sdhci_get_cd_nogpio(struct mmc_host *mmc);
>
Thanks, Victor Shih
Hi, Adrian
On Fri, Jan 6, 2023 at 5:28 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > Sdhci_uhs2_do_detect_init() is a sdhci version of mmc's uhs2_detect_init
> > operation. After detected, the host's UHS-II capabilities will be set up
> > here and interrupts will also be enabled.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/host/sdhci-uhs2.c | 117 ++++++++++++++++++++++++++++++++++
> > 1 file changed, 117 insertions(+)
> >
> > diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> > index 3d52d35a91a5..52587025d5e3 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.c
> > +++ b/drivers/mmc/host/sdhci-uhs2.c
> > @@ -335,6 +335,123 @@ int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > * *
> > \*****************************************************************************/
> >
> > +static int sdhci_uhs2_interface_detect(struct sdhci_host *host)
> > +{
> > + /* 100ms */
> > + int timeout = 100000;
> > + u32 val;
> > +
> > + udelay(200); /* wait for 200us before check */
> > +
> > + if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_IF_DETECT),
> > + 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
> > + pr_warn("%s: not detect UHS2 interface in 200us.\n", mmc_hostname(host->mmc));
> > + sdhci_dumpregs(host);
> > + return -EIO;
> > + }
> > +
> > + /* Enable UHS2 error interrupts */
> > + sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
> > +
> > + /* 150ms */
> > + timeout = 150000;
> > + if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_LANE_SYNC),
> > + 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
> > + pr_warn("%s: UHS2 Lane sync fail in 150ms.\n", mmc_hostname(host->mmc));
> > + sdhci_dumpregs(host);
> > + return -EIO;
> > + }
> > +
> > + DBG("%s: UHS2 Lane synchronized in UHS2 mode, PHY is initialized.\n",
> > + mmc_hostname(host->mmc));
> > + return 0;
> > +}
> > +
> > +static int sdhci_uhs2_init(struct sdhci_host *host)
> > +{
> > + u16 caps_ptr = 0;
> > + u32 caps_gen = 0;
> > + u32 caps_phy = 0;
> > + u32 caps_tran[2] = {0, 0};
> > + struct mmc_host *mmc = host->mmc;
> > +
> > + caps_ptr = sdhci_readw(host, SDHCI_UHS2_CAPS_PTR);
> > + if (caps_ptr < 0x100 || caps_ptr > 0x1FF) {
> > + pr_err("%s: SDHCI_UHS2_CAPS_PTR(%d) is wrong.\n",
> > + mmc_hostname(mmc), caps_ptr);
> > + return -ENODEV;
> > + }
> > + caps_gen = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_OFFSET);
> > + caps_phy = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_PHY_OFFSET);
> > + caps_tran[0] = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_TRAN_OFFSET);
> > + caps_tran[1] = sdhci_readl(host, caps_ptr + SDHCI_UHS2_CAPS_TRAN_1_OFFSET);
> > +
> > + /* General Caps */
> > + mmc->uhs2_caps.dap = caps_gen & SDHCI_UHS2_CAPS_DAP_MASK;
> > + mmc->uhs2_caps.gap = FIELD_GET(SDHCI_UHS2_CAPS_GAP_MASK, caps_gen);
> > + mmc->uhs2_caps.n_lanes = FIELD_GET(SDHCI_UHS2_CAPS_LANE_MASK, caps_gen);
> > + mmc->uhs2_caps.addr64 = (caps_gen & SDHCI_UHS2_CAPS_ADDR_64) ? 1 : 0;
> > + mmc->uhs2_caps.card_type = FIELD_GET(SDHCI_UHS2_CAPS_DEV_TYPE_MASK, caps_gen);
> > +
> > + /* PHY Caps */
> > + mmc->uhs2_caps.phy_rev = caps_phy & SDHCI_UHS2_CAPS_PHY_REV_MASK;
> > + mmc->uhs2_caps.speed_range = FIELD_GET(SDHCI_UHS2_CAPS_PHY_RANGE_MASK, caps_phy);
> > + mmc->uhs2_caps.n_lss_sync = FIELD_GET(SDHCI_UHS2_CAPS_PHY_N_LSS_SYN_MASK, caps_phy);
> > + mmc->uhs2_caps.n_lss_dir = FIELD_GET(SDHCI_UHS2_CAPS_PHY_N_LSS_DIR_MASK, caps_phy);
> > + if (mmc->uhs2_caps.n_lss_sync == 0)
> > + mmc->uhs2_caps.n_lss_sync = 16 << 2;
> > + else
> > + mmc->uhs2_caps.n_lss_sync <<= 2;
> > + if (mmc->uhs2_caps.n_lss_dir == 0)
> > + mmc->uhs2_caps.n_lss_dir = 16 << 3;
> > + else
> > + mmc->uhs2_caps.n_lss_dir <<= 3;
> > +
> > + /* LINK/TRAN Caps */
> > + mmc->uhs2_caps.link_rev = caps_tran[0] & SDHCI_UHS2_CAPS_TRAN_LINK_REV_MASK;
> > + mmc->uhs2_caps.n_fcu = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_N_FCU_MASK, caps_tran[0]);
> > + if (mmc->uhs2_caps.n_fcu == 0)
> > + mmc->uhs2_caps.n_fcu = 256;
> > + mmc->uhs2_caps.host_type = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_HOST_TYPE_MASK, caps_tran[0]);
> > + mmc->uhs2_caps.maxblk_len = FIELD_GET(SDHCI_UHS2_CAPS_TRAN_BLK_LEN_MASK, caps_tran[0]);
> > + mmc->uhs2_caps.n_data_gap = caps_tran[1] & SDHCI_UHS2_CAPS_TRAN_1_N_DATA_GAP_MASK;
> > +
> > + return 0;
> > +}
> > +
> > +static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc)
> > +{
> > + struct sdhci_host *host = mmc_priv(mmc);
> > + int ret = -EIO;
> > +
> > + DBG("Begin do uhs2 detect init.\n");
> > +
> > + if (sdhci_uhs2_interface_detect(host)) {
> > + pr_warn("%s: cannot detect UHS2 interface.\n",
> > + mmc_hostname(host->mmc));
> > + goto out;
> > + }
> > +
> > + if (sdhci_uhs2_init(host)) {
> > + pr_warn("%s: UHS2 init fail.\n", mmc_hostname(host->mmc));
> > + goto out;
> > + }
> > +
> > + /* Init complete, do soft reset and enable UHS2 error irqs. */
> > + host->ops->uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
>
> host->ops->uhs2_reset -> sdhci_uhs2_reset
>
I will update it in V7 version.
> > + sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
> > + /*
> > + * N.B SDHCI_INT_ENABLE and SDHCI_SIGNAL_ENABLE was cleared
> > + * by SDHCI_UHS2_SW_RESET_SD
> > + */
> > + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
> > + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
> > +
> > + ret = 0;
> > +out:
> > + return ret;
> > +}
> > +
> > static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> > {
> > host->mmc_host_ops.start_signal_voltage_switch =
>
Thanks, Victor Shih
Hi, Adrian
On Fri, Jan 6, 2023 at 5:28 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > This is a sdhci version of mmc's uhs2_set_reg operation.
> > UHS-II interface (related registers) will be initialised here.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/host/sdhci-uhs2.c | 92 +++++++++++++++++++++++++++++++++++
> > drivers/mmc/host/sdhci.c | 11 +++++
> > 2 files changed, 103 insertions(+)
> >
> > diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> > index c7e411fa0a6e..de15a6559702 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.c
> > +++ b/drivers/mmc/host/sdhci-uhs2.c
> > @@ -276,6 +276,48 @@ static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > sdhci_set_clock(host, host->clock);
> > }
> >
> > +static void sdhci_uhs2_set_config(struct sdhci_host *host)
> > +{
> > + u32 value;
> > + u16 sdhci_uhs2_set_ptr = sdhci_readw(host, SDHCI_UHS2_SETTINGS_PTR);
> > + u16 sdhci_uhs2_gen_set_reg = (sdhci_uhs2_set_ptr + 0);
> > + u16 sdhci_uhs2_phy_set_reg = (sdhci_uhs2_set_ptr + 4);
> > + u16 sdhci_uhs2_tran_set_reg = (sdhci_uhs2_set_ptr + 8);
> > + u16 sdhci_uhs2_tran_set_1_reg = (sdhci_uhs2_set_ptr + 12);
> > +
> > + /* Set Gen Settings */
> > + value = FIELD_PREP(SDHCI_UHS2_GEN_SETTINGS_N_LANES_MASK, host->mmc->uhs2_caps.n_lanes_set);
> > + sdhci_writel(host, value, sdhci_uhs2_gen_set_reg);
> > +
> > + /* Set PHY Settings */
> > + value = FIELD_PREP(SDHCI_UHS2_PHY_N_LSS_DIR_MASK, host->mmc->uhs2_caps.n_lss_dir_set) |
> > + FIELD_PREP(SDHCI_UHS2_PHY_N_LSS_SYN_MASK, host->mmc->uhs2_caps.n_lss_sync_set);
> > + if (host->mmc->flags & MMC_UHS2_SPEED_B)
> > + value |= SDHCI_UHS2_PHY_SET_SPEED_B;
> > + sdhci_writel(host, value, sdhci_uhs2_phy_set_reg);
> > +
> > + /* Set LINK-TRAN Settings */
> > + value = FIELD_PREP(SDHCI_UHS2_TRAN_RETRY_CNT_MASK, host->mmc->uhs2_caps.max_retry_set) |
> > + FIELD_PREP(SDHCI_UHS2_TRAN_N_FCU_MASK, host->mmc->uhs2_caps.n_fcu_set);
> > + sdhci_writel(host, value, sdhci_uhs2_tran_set_reg);
> > + sdhci_writel(host, host->mmc->uhs2_caps.n_data_gap_set, sdhci_uhs2_tran_set_1_reg);
> > +}
> > +
> > +static int sdhci_uhs2_check_dormant(struct sdhci_host *host)
> > +{
> > + u32 val;
> > + /* 100ms */
> > + int timeout = 100000;
> > +
> > + if (read_poll_timeout_atomic(sdhci_readl, val, (val & SDHCI_UHS2_IN_DORMANT_STATE),
> > + 100, timeout, true, host, SDHCI_PRESENT_STATE)) {
> > + pr_warn("%s: UHS2 IN_DORMANT fail in 100ms.\n", mmc_hostname(host->mmc));
> > + sdhci_dumpregs(host);
> > + return -EIO;
> > + }
> > + return 0;
> > +}
> > +
> > /*****************************************************************************\
> > * *
> > * MMC callbacks *
> > @@ -361,6 +403,55 @@ static int sdhci_uhs2_enable_clk(struct mmc_host *mmc)
> > return 0;
> > }
> >
> > +static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc);
> > +
> > +static int sdhci_uhs2_control(struct mmc_host *mmc, enum sd_uhs2_operation op)
> > +{
> > + struct sdhci_host *host = mmc_priv(mmc);
> > + int err = 0;
> > + u16 sdhci_uhs2_set_ptr = sdhci_readw(host, SDHCI_UHS2_SETTINGS_PTR);
> > + u16 sdhci_uhs2_phy_set_reg = (sdhci_uhs2_set_ptr + 4);
> > +
> > + DBG("Begin uhs2 control, act %d.\n", op);
> > +
> > + switch (op) {
> > + case UHS2_PHY_INIT:
> > + err = sdhci_uhs2_do_detect_init(mmc);
> > + break;
> > + case UHS2_SET_CONFIG:
> > + sdhci_uhs2_set_config(host);
> > + break;
> > + case UHS2_ENABLE_INT:
> > + sdhci_uhs2_clear_set_irqs(host, 0, SDHCI_INT_CARD_INT);
> > + break;
> > + case UHS2_DISABLE_INT:
> > + sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_CARD_INT, 0);
> > + break;
> > + case UHS2_SET_SPEED_B:
> > + sdhci_writeb(host, SDHCI_UHS2_PHY_SET_SPEED_B, sdhci_uhs2_phy_set_reg);
> > + break;
> > + case UHS2_CHECK_DORMANT:
> > + err = sdhci_uhs2_check_dormant(host);
> > + break;
> > + case UHS2_DISABLE_CLK:
> > + err = sdhci_uhs2_disable_clk(mmc);
> > + break;
> > + case UHS2_ENABLE_CLK:
> > + err = sdhci_uhs2_enable_clk(mmc);
> > + break;
> > + case UHS2_POST_ATTACH_SD:
> > + host->ops->uhs2_post_attach_sd(host);
> > + break;
> > + default:
> > + pr_err("%s: input sd uhs2 operation %d is wrong!\n",
> > + mmc_hostname(host->mmc), op);
> > + err = -EIO;
> > + break;
> > + }
> > +
> > + return err;
> > +}
> > +
> > /*****************************************************************************\
> > * *
> > * Driver init/exit *
> > @@ -489,6 +580,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> > host->mmc_host_ops.start_signal_voltage_switch =
> > sdhci_uhs2_start_signal_voltage_switch;
> > host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
> > + host->mmc_host_ops.uhs2_control = sdhci_uhs2_control;
> >
> > return 0;
> > }
> > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> > index 49bbdc155b2b..3656e66fcca7 100644
> > --- a/drivers/mmc/host/sdhci.c
> > +++ b/drivers/mmc/host/sdhci.c
> > @@ -234,6 +234,17 @@ void sdhci_reset(struct sdhci_host *host, u8 mask)
> > }
> > EXPORT_SYMBOL_GPL(sdhci_reset);
> >
> > +void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
> > +{
> > + u32 ier;
> > +
> > + ier = sdhci_readl(host, SDHCI_INT_ENABLE);
> > + ier &= ~clear;
> > + ier |= set;
> > + sdhci_writel(host, ier, SDHCI_INT_ENABLE);
> > + sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
> > +}
>
> Not used anymore
>
I will drop it and update in V7 version.
> > +
> > static bool sdhci_do_reset(struct sdhci_host *host, u8 mask)
> > {
> > if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
>
Thanks, Victor Shih
Hi, Adrian
On Fri, Jan 6, 2023 at 5:29 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > This is a sdhci version of mmc's request operation.
> > It covers both UHS-I and UHS-II.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/host/sdhci-uhs2.c | 418 ++++++++++++++++++++++++++++++++++
> > drivers/mmc/host/sdhci.c | 78 ++++---
> > drivers/mmc/host/sdhci.h | 14 ++
> > 3 files changed, 481 insertions(+), 29 deletions(-)
> >
> > diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> > index de15a6559702..7a34f66c4215 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.c
> > +++ b/drivers/mmc/host/sdhci-uhs2.c
> > @@ -15,6 +15,7 @@
> > #include <linux/iopoll.h>
> > #include <linux/bitfield.h>
> > #include <linux/ktime.h>
> > +#include <linux/mmc/mmc.h>
> >
> > #include "sdhci.h"
> > #include "sdhci-uhs2.h"
> > @@ -25,6 +26,8 @@
> > #define SDHCI_UHS2_DUMP(f, x...) \
> > pr_err("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x)
> >
> > +#define UHS2_ARG_IOADR_MASK 0xfff
> > +
> > void sdhci_uhs2_dump_regs(struct sdhci_host *host)
> > {
> > if (!(host->mmc->flags & MMC_UHS2_SUPPORT))
> > @@ -59,6 +62,11 @@ EXPORT_SYMBOL_GPL(sdhci_uhs2_dump_regs);
> > * *
> > \*****************************************************************************/
> >
> > +static inline u16 uhs2_dev_cmd(struct mmc_command *cmd)
> > +{
> > + return be16_to_cpu((__be16)cmd->uhs2_cmd->arg) & UHS2_ARG_IOADR_MASK;
> > +}
> > +
> > static inline int mmc_opt_regulator_set_ocr(struct mmc_host *mmc,
> > struct regulator *supply,
> > unsigned short vdd_bit)
> > @@ -452,6 +460,415 @@ static int sdhci_uhs2_control(struct mmc_host *mmc, enum sd_uhs2_operation op)
> > return err;
> > }
> >
> > +/*****************************************************************************\
> > + * *
> > + * Core functions *
> > + * *
> > +\*****************************************************************************/
> > +
> > +static void sdhci_uhs2_prepare_data(struct sdhci_host *host,
> > + struct mmc_command *cmd)
> > +{
> > + struct mmc_data *data = cmd->data;
> > +
> > + sdhci_initialize_data(host, data);
> > +
> > + sdhci_prepare_dma(host, data);
> > +
> > + sdhci_writew(host, data->blksz, SDHCI_UHS2_BLOCK_SIZE);
> > + sdhci_writew(host, data->blocks, SDHCI_UHS2_BLOCK_COUNT);
> > +}
> > +
> > +static void sdhci_uhs2_finish_data(struct sdhci_host *host)
> > +{
> > + struct mmc_data *data = host->data;
> > +
> > + __sdhci_finish_data_common(host);
> > +
> > + __sdhci_finish_mrq(host, data->mrq);
> > +}
> > +
> > +static void sdhci_uhs2_set_transfer_mode(struct sdhci_host *host,
> > + struct mmc_command *cmd)
> > +{
> > + u16 mode;
> > + struct mmc_data *data = cmd->data;
> > +
> > + if (!data) {
> > + /* clear Auto CMD settings for no data CMDs */
> > + if (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_TRANS_ABORT) {
> > + mode = 0;
> > + } else {
> > + mode = sdhci_readw(host, SDHCI_UHS2_TRANS_MODE);
> > + if (cmd->opcode == MMC_STOP_TRANSMISSION || cmd->opcode == MMC_ERASE)
> > + mode |= SDHCI_UHS2_TRNS_WAIT_EBSY;
> > + else
> > + /* send status mode */
> > + if (cmd->opcode == MMC_SEND_STATUS)
> > + mode = 0;
> > + }
> > +
> > + DBG("UHS2 no data trans mode is 0x%x.\n", mode);
> > +
> > + sdhci_writew(host, mode, SDHCI_UHS2_TRANS_MODE);
> > + return;
> > + }
> > +
> > + WARN_ON(!host->data);
> > +
> > + mode = SDHCI_UHS2_TRNS_BLK_CNT_EN | SDHCI_UHS2_TRNS_WAIT_EBSY;
> > + if (data->flags & MMC_DATA_WRITE)
> > + mode |= SDHCI_UHS2_TRNS_DATA_TRNS_WRT;
> > +
> > + if (data->blocks == 1 &&
> > + data->blksz != 512 &&
> > + cmd->opcode != MMC_READ_SINGLE_BLOCK &&
> > + cmd->opcode != MMC_WRITE_BLOCK) {
> > + mode &= ~SDHCI_UHS2_TRNS_BLK_CNT_EN;
> > + mode |= SDHCI_UHS2_TRNS_BLK_BYTE_MODE;
> > + }
> > +
> > + if (host->flags & SDHCI_REQ_USE_DMA)
> > + mode |= SDHCI_UHS2_TRNS_DMA;
> > +
> > + if ((host->mmc->uhs2_ios.is_2L_HD_mode) && !cmd->uhs2_tmode0_flag)
> > + mode |= SDHCI_UHS2_TRNS_2L_HD;
> > +
> > + sdhci_writew(host, mode, SDHCI_UHS2_TRANS_MODE);
> > +
> > + DBG("UHS2 trans mode is 0x%x.\n", mode);
> > +}
> > +
> > +static void __sdhci_uhs2_send_command(struct sdhci_host *host,
> > + struct mmc_command *cmd)
> > +{
> > + int i, j;
> > + int cmd_reg;
> > +
> > + i = 0;
> > + sdhci_writel(host,
> > + ((u32)cmd->uhs2_cmd->arg << 16) |
> > + (u32)cmd->uhs2_cmd->header,
> > + SDHCI_UHS2_CMD_PACKET + i);
> > + i += 4;
> > +
> > + /*
> > + * Per spec, playload (config) should be MSB before sending out.
> > + * But we don't need convert here because had set payload as
> > + * MSB when preparing config read/write commands.
> > + */
> > + for (j = 0; j < cmd->uhs2_cmd->payload_len / sizeof(u32); j++) {
> > + sdhci_writel(host, *(cmd->uhs2_cmd->payload + j), SDHCI_UHS2_CMD_PACKET + i);
> > + i += 4;
> > + }
> > +
> > + for ( ; i < SDHCI_UHS2_CMD_PACK_MAX_LEN; i += 4)
> > + sdhci_writel(host, 0, SDHCI_UHS2_CMD_PACKET + i);
> > +
> > + DBG("UHS2 CMD packet_len = %d.\n", cmd->uhs2_cmd->packet_len);
> > + for (i = 0; i < cmd->uhs2_cmd->packet_len; i++)
> > + DBG("UHS2 CMD_PACKET[%d] = 0x%x.\n", i,
> > + sdhci_readb(host, SDHCI_UHS2_CMD_PACKET + i));
> > +
> > + cmd_reg = FIELD_PREP(SDHCI_UHS2_CMD_PACK_LEN_MASK, cmd->uhs2_cmd->packet_len);
> > + if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC)
> > + cmd_reg |= SDHCI_UHS2_CMD_DATA;
> > + if (cmd->opcode == MMC_STOP_TRANSMISSION)
> > + cmd_reg |= SDHCI_UHS2_CMD_CMD12;
> > +
> > + /* UHS2 Native ABORT */
> > + if ((cmd->uhs2_cmd->header & UHS2_NATIVE_PACKET) &&
> > + (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_TRANS_ABORT))
> > + cmd_reg |= SDHCI_UHS2_CMD_TRNS_ABORT;
> > +
> > + /* UHS2 Native DORMANT */
> > + if ((cmd->uhs2_cmd->header & UHS2_NATIVE_PACKET) &&
> > + (uhs2_dev_cmd(cmd) == UHS2_DEV_CMD_GO_DORMANT_STATE))
> > + cmd_reg |= SDHCI_UHS2_CMD_DORMANT;
> > +
> > + DBG("0x%x is set to UHS2 CMD register.\n", cmd_reg);
> > +
> > + sdhci_writew(host, cmd_reg, SDHCI_UHS2_CMD);
> > +}
> > +
> > +static bool sdhci_uhs2_send_command(struct sdhci_host *host,
> > + struct mmc_command *cmd)
> > +{
> > + int flags;
> > + u32 mask;
> > + unsigned long timeout;
> > +
> > + WARN_ON(host->cmd);
> > +
> > + /* Initially, a command has no error */
> > + cmd->error = 0;
> > +
> > + if (cmd->opcode == MMC_STOP_TRANSMISSION)
> > + cmd->flags |= MMC_RSP_BUSY;
> > +
> > + mask = SDHCI_CMD_INHIBIT;
> > +
> > + if (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask)
> > + return false;
> > +
> > + host->cmd = cmd;
> > + host->data_timeout = 0;
> > + if (sdhci_data_line_cmd(cmd)) {
> > + WARN_ON(host->data_cmd);
> > + host->data_cmd = cmd;
> > + __sdhci_uhs2_set_timeout(host);
> > + }
> > +
> > + if (cmd->data)
> > + sdhci_uhs2_prepare_data(host, cmd);
> > +
> > + sdhci_uhs2_set_transfer_mode(host, cmd);
> > +
> > + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
> > + WARN_ONCE(1, "Unsupported response type!\n");
> > + /*
> > + * This does not happen in practice because 136-bit response
> > + * commands never have busy waiting, so rather than complicate
> > + * the error path, just remove busy waiting and continue.
> > + */
> > + cmd->flags &= ~MMC_RSP_BUSY;
> > + }
> > +
> > + if (!(cmd->flags & MMC_RSP_PRESENT))
> > + flags = SDHCI_CMD_RESP_NONE;
> > + else if (cmd->flags & MMC_RSP_136)
> > + flags = SDHCI_CMD_RESP_LONG;
> > + else if (cmd->flags & MMC_RSP_BUSY)
> > + flags = SDHCI_CMD_RESP_SHORT_BUSY;
> > + else
> > + flags = SDHCI_CMD_RESP_SHORT;
> > +
> > + if (cmd->flags & MMC_RSP_CRC)
> > + flags |= SDHCI_CMD_CRC;
> > + if (cmd->flags & MMC_RSP_OPCODE)
> > + flags |= SDHCI_CMD_INDEX;
> > +
> > + timeout = jiffies;
> > + if (host->data_timeout)
> > + timeout += nsecs_to_jiffies(host->data_timeout);
> > + else if (!cmd->data && cmd->busy_timeout > 9000)
> > + timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
> > + else
> > + timeout += 10 * HZ;
> > + sdhci_mod_timer(host, cmd->mrq, timeout);
> > +
> > + __sdhci_uhs2_send_command(host, cmd);
> > +
> > + return true;
> > +}
> > +
> > +static bool sdhci_uhs2_send_command_retry(struct sdhci_host *host,
> > + struct mmc_command *cmd,
> > + unsigned long flags)
> > + __releases(host->lock)
> > + __acquires(host->lock)
> > +{
> > + struct mmc_command *deferred_cmd = host->deferred_cmd;
> > + int timeout = 10; /* Approx. 10 ms */
> > + bool present;
> > +
> > + while (!sdhci_uhs2_send_command(host, cmd)) {
> > + if (!timeout--) {
> > + pr_err("%s: Controller never released inhibit bit(s).\n",
> > + mmc_hostname(host->mmc));
> > + sdhci_dumpregs(host);
> > + cmd->error = -EIO;
> > + return false;
> > + }
> > +
> > + spin_unlock_irqrestore(&host->lock, flags);
> > +
> > + usleep_range(1000, 1250);
> > +
> > + present = host->mmc->ops->get_cd(host->mmc);
> > +
> > + spin_lock_irqsave(&host->lock, flags);
> > +
> > + /* A deferred command might disappear, handle that */
> > + if (cmd == deferred_cmd && cmd != host->deferred_cmd)
> > + return true;
> > +
> > + if (sdhci_present_error(host, cmd, present))
> > + return false;
> > + }
> > +
> > + if (cmd == host->deferred_cmd)
> > + host->deferred_cmd = NULL;
> > +
> > + return true;
> > +}
> > +
> > +static void __sdhci_uhs2_finish_command(struct sdhci_host *host)
> > +{
> > + struct mmc_command *cmd = host->cmd;
> > + u8 resp;
> > + u8 ecode;
> > + bool bReadA0 = 0;
> > + int i;
> > +
> > + if (host->mmc->card) {
> > + if (host->mmc->card->uhs2_state & MMC_UHS2_INITIALIZED) {
> > + resp = sdhci_readb(host, SDHCI_UHS2_RESPONSE + 2);
> > + if (resp & UHS2_RES_NACK_MASK) {
> > + ecode = (resp >> UHS2_RES_ECODE_POS) & UHS2_RES_ECODE_MASK;
> > + pr_err("%s: NACK is got, ECODE=0x%x.\n",
> > + mmc_hostname(host->mmc), ecode);
> > + }
> > + bReadA0 = 1;
> > + }
> > + }
> > +
> > + if (cmd->uhs2_resp &&
> > + cmd->uhs2_resp_len && cmd->uhs2_resp_len <= 20) {
> > + /* Get whole response of some native CCMD, like
> > + * DEVICE_INIT, ENUMERATE.
> > + */
> > + for (i = 0; i < cmd->uhs2_resp_len; i++)
> > + cmd->uhs2_resp[i] = sdhci_readb(host, SDHCI_UHS2_RESPONSE + i);
> > + } else {
> > + /* Get SD CMD response and Payload for some read
> > + * CCMD, like INQUIRY_CFG.
> > + */
> > + /* Per spec (p136), payload field is divided into
> > + * a unit of DWORD and transmission order within
> > + * a DWORD is big endian.
> > + */
> > + if (!bReadA0)
> > + sdhci_readl(host, SDHCI_UHS2_RESPONSE);
> > + for (i = 4; i < 20; i += 4) {
> > + cmd->resp[i / 4 - 1] =
> > + (sdhci_readb(host,
> > + SDHCI_UHS2_RESPONSE + i) << 24) |
> > + (sdhci_readb(host,
> > + SDHCI_UHS2_RESPONSE + i + 1)
> > + << 16) |
> > + (sdhci_readb(host,
> > + SDHCI_UHS2_RESPONSE + i + 2)
> > + << 8) |
> > + sdhci_readb(host, SDHCI_UHS2_RESPONSE + i + 3);
> > + }
> > + }
> > +}
> > +
> > +static void sdhci_uhs2_finish_command(struct sdhci_host *host)
> > +{
> > + struct mmc_command *cmd = host->cmd;
> > +
> > + __sdhci_uhs2_finish_command(host);
> > +
> > + host->cmd = NULL;
> > +
> > + if (cmd->mrq->cap_cmd_during_tfr && cmd == cmd->mrq->cmd)
> > + mmc_command_done(host->mmc, cmd->mrq);
> > +
> > + /*
> > + * The host can send and interrupt when the busy state has
> > + * ended, allowing us to wait without wasting CPU cycles.
> > + * The busy signal uses DAT0 so this is similar to waiting
> > + * for data to complete.
> > + *
> > + * Note: The 1.0 specification is a bit ambiguous about this
> > + * feature so there might be some problems with older
> > + * controllers.
> > + */
> > + if (cmd->flags & MMC_RSP_BUSY) {
> > + if (cmd->data) {
> > + DBG("Cannot wait for busy signal when also doing a data transfer");
> > + } else if (!(host->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) &&
> > + cmd == host->data_cmd) {
> > + /* Command complete before busy is ended */
> > + return;
> > + }
> > + }
> > +
> > + /* Processed actual command. */
> > + if (host->data && host->data_early)
> > + sdhci_uhs2_finish_data(host);
> > +
> > + if (!cmd->data)
> > + __sdhci_finish_mrq(host, cmd->mrq);
> > +}
> > +
> > +void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
> > +{
> > + struct sdhci_host *host = mmc_priv(mmc);
> > + struct mmc_command *cmd;
> > + unsigned long flags;
> > + bool present;
> > +
> > + if (!(sdhci_uhs2_mode(host))) {
> > + sdhci_request(mmc, mrq);
> > + return;
> > + }
> > +
> > + mrq->stop = NULL;
> > + mrq->sbc = NULL;
> > + if (mrq->data)
> > + mrq->data->stop = NULL;
> > +
> > + /* Firstly check card presence */
> > + present = mmc->ops->get_cd(mmc);
> > +
> > + spin_lock_irqsave(&host->lock, flags);
> > +
> > + if (sdhci_present_error(host, mrq->cmd, present))
> > + goto out_finish;
> > +
> > + cmd = mrq->cmd;
> > +
> > + if (!sdhci_uhs2_send_command(host, cmd))
> > + goto out_finish;
> > +
> > + spin_unlock_irqrestore(&host->lock, flags);
> > +
> > + return;
> > +
> > +out_finish:
> > + sdhci_finish_mrq(host, mrq);
> > + spin_unlock_irqrestore(&host->lock, flags);
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_uhs2_request);
> > +
> > +int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq)
> > +{
> > + struct sdhci_host *host = mmc_priv(mmc);
> > + struct mmc_command *cmd;
> > + unsigned long flags;
> > + int ret = 0;
> > +
> > + if (!host->mmc->flags & MMC_UHS2_SUPPORT)
> > + return sdhci_request_atomic(mmc, mrq);
> > +
> > + spin_lock_irqsave(&host->lock, flags);
> > +
> > + if (sdhci_present_error(host, mrq->cmd, true)) {
> > + sdhci_finish_mrq(host, mrq);
> > + goto out_finish;
> > + }
> > +
> > + cmd = mrq->cmd;
> > +
> > + /*
> > + * The HSQ may send a command in interrupt context without polling
> > + * the busy signaling, which means we should return BUSY if controller
> > + * has not released inhibit bits to allow HSQ trying to send request
> > + * again in non-atomic context. So we should not finish this request
> > + * here.
> > + */
> > + if (!sdhci_uhs2_send_command(host, cmd))
> > + ret = -EBUSY;
> > +
> > +out_finish:
> > + spin_unlock_irqrestore(&host->lock, flags);
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_uhs2_request_atomic);
> > +
> > /*****************************************************************************\
> > * *
> > * Driver init/exit *
> > @@ -581,6 +998,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> > sdhci_uhs2_start_signal_voltage_switch;
> > host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
> > host->mmc_host_ops.uhs2_control = sdhci_uhs2_control;
> > + host->mmc_host_ops.request = sdhci_uhs2_request;
> >
> > return 0;
> > }
> > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> > index 3656e66fcca7..c547fc3b8b47 100644
> > --- a/drivers/mmc/host/sdhci.c
> > +++ b/drivers/mmc/host/sdhci.c
> > @@ -47,8 +47,6 @@
> > static unsigned int debug_quirks = 0;
> > static unsigned int debug_quirks2;
> >
> > -static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
> > -
> > void sdhci_dumpregs(struct sdhci_host *host)
> > {
> > SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n");
> > @@ -147,10 +145,11 @@ void sdhci_enable_v4_mode(struct sdhci_host *host)
> > }
> > EXPORT_SYMBOL_GPL(sdhci_enable_v4_mode);
> >
> > -static inline bool sdhci_data_line_cmd(struct mmc_command *cmd)
> > +bool sdhci_data_line_cmd(struct mmc_command *cmd)
> > {
> > return cmd->data || cmd->flags & MMC_RSP_BUSY;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_data_line_cmd);
> >
> > static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
> > {
> > @@ -424,7 +423,7 @@ static void __sdhci_led_activate(struct sdhci_host *host)
> > sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
> > }
> >
> > -static void __sdhci_led_deactivate(struct sdhci_host *host)
> > +void __sdhci_led_deactivate(struct sdhci_host *host)
> > {
> > u8 ctrl;
> >
> > @@ -435,6 +434,7 @@ static void __sdhci_led_deactivate(struct sdhci_host *host)
> > ctrl &= ~SDHCI_CTRL_LED;
> > sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
> > }
> > +EXPORT_SYMBOL_GPL(__sdhci_led_deactivate);
>
> Do not need to export __sdhci_led_deactivate
>
> >
> > #if IS_REACHABLE(CONFIG_LEDS_CLASS)
> > static void sdhci_led_control(struct led_classdev *led,
> > @@ -513,14 +513,15 @@ static inline void sdhci_led_deactivate(struct sdhci_host *host)
> >
> > #endif
> >
> > -static void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq,
> > - unsigned long timeout)
> > +void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq,
> > + unsigned long timeout)
> > {
> > if (sdhci_data_line_cmd(mrq->cmd))
> > mod_timer(&host->data_timer, timeout);
> > else
> > mod_timer(&host->timer, timeout);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_mod_timer);
> >
> > static void sdhci_del_timer(struct sdhci_host *host, struct mmc_request *mrq)
> > {
> > @@ -1087,8 +1088,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
> > __sdhci_set_timeout(host, cmd);
> > }
> >
> > -static void sdhci_initialize_data(struct sdhci_host *host,
> > - struct mmc_data *data)
> > +void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data)
> > {
> > WARN_ON(host->data);
> >
> > @@ -1101,6 +1101,7 @@ static void sdhci_initialize_data(struct sdhci_host *host,
> > host->data_early = 0;
> > host->data->bytes_xfered = 0;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_initialize_data);
> >
> > static inline void sdhci_set_block_info(struct sdhci_host *host,
> > struct mmc_data *data)
> > @@ -1123,12 +1124,8 @@ static inline void sdhci_set_block_info(struct sdhci_host *host,
> > }
> > }
> >
> > -static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> > +void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data)
> > {
> > - struct mmc_data *data = cmd->data;
> > -
> > - sdhci_initialize_data(host, data);
> > -
> > if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
> > struct scatterlist *sg;
> > unsigned int length_mask, offset_mask;
> > @@ -1213,6 +1210,16 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> > }
> >
> > sdhci_set_transfer_irqs(host);
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_prepare_dma);
> > +
> > +static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> > +{
> > + struct mmc_data *data = cmd->data;
> > +
> > + sdhci_initialize_data(host, data);
> > +
> > + sdhci_prepare_dma(host, data);
> >
> > sdhci_set_block_info(host, data);
> > }
> > @@ -1255,8 +1262,7 @@ static struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
> > return data->flags & MMC_DATA_WRITE ? host->tx_chan : host->rx_chan;
> > }
> >
> > -static int sdhci_external_dma_setup(struct sdhci_host *host,
> > - struct mmc_command *cmd)
> > +int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd)
> > {
> > int ret, i;
> > enum dma_transfer_direction dir;
> > @@ -1309,8 +1315,9 @@ static int sdhci_external_dma_setup(struct sdhci_host *host,
> >
> > return ret;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_external_dma_setup);
>
> Do not need to export sdhci_external_dma_setup
>
I will update it in V7 version.
> >
> > -static void sdhci_external_dma_release(struct sdhci_host *host)
> > +void sdhci_external_dma_release(struct sdhci_host *host)
> > {
> > if (host->tx_chan) {
> > dma_release_channel(host->tx_chan);
> > @@ -1324,9 +1331,10 @@ static void sdhci_external_dma_release(struct sdhci_host *host)
> >
> > sdhci_switch_external_dma(host, false);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_external_dma_release);
>
> Do not need to export sdhci_external_dma_release
>
I will update it in V7 version.
> >
> > -static void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
> > - struct mmc_command *cmd)
> > +void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
> > + struct mmc_command *cmd)
> > {
> > struct mmc_data *data = cmd->data;
> >
> > @@ -1337,6 +1345,7 @@ static void __sdhci_external_dma_prepare_data(struct sdhci_host *host,
> >
> > sdhci_set_block_info(host, data);
> > }
> > +EXPORT_SYMBOL(__sdhci_external_dma_prepare_data);
>
> Do not need to export __sdhci_external_dma_prepare_data
>
I will update it in V7 version.
> >
> > static void sdhci_external_dma_prepare_data(struct sdhci_host *host,
> > struct mmc_command *cmd)
> > @@ -1351,8 +1360,8 @@ static void sdhci_external_dma_prepare_data(struct sdhci_host *host,
> > }
> > }
> >
> > -static void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
> > - struct mmc_command *cmd)
> > +void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
> > + struct mmc_command *cmd)
> > {
> > struct dma_chan *chan;
> >
> > @@ -1363,6 +1372,7 @@ static void sdhci_external_dma_pre_transfer(struct sdhci_host *host,
> > if (chan)
> > dma_async_issue_pending(chan);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_external_dma_pre_transfer);
>
> Do not need to export sdhci_external_dma_pre_transfer
>
I will update it in V7 version.
> >
> > #else
> >
> > @@ -1414,8 +1424,7 @@ static inline bool sdhci_auto_cmd23(struct sdhci_host *host,
> > return mrq->sbc && (host->flags & SDHCI_AUTO_CMD23);
> > }
> >
> > -static inline bool sdhci_manual_cmd23(struct sdhci_host *host,
> > - struct mmc_request *mrq)
> > +static inline bool sdhci_manual_cmd23(struct sdhci_host *host, struct mmc_request *mrq)
>
> Please do not make whitespace changes if nothing else changes
>
I will update it in V7 version.
> > {
> > return mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23);
> > }
> > @@ -1530,7 +1539,7 @@ static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
> > WARN_ON(i >= SDHCI_MAX_MRQS);
> > }
> >
> > -static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> > +void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> > {
> > if (host->cmd && host->cmd->mrq == mrq)
> > host->cmd = NULL;
> > @@ -1554,15 +1563,17 @@ static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> > if (!sdhci_has_requests(host))
> > sdhci_led_deactivate(host);
> > }
> > +EXPORT_SYMBOL_GPL(__sdhci_finish_mrq);
> >
> > -static void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> > +void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> > {
> > __sdhci_finish_mrq(host, mrq);
> >
> > queue_work(host->complete_wq, &host->complete_work);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_finish_mrq);
> >
> > -static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
> > +void __sdhci_finish_data_common(struct sdhci_host *host)
> > {
> > struct mmc_command *data_cmd = host->data_cmd;
> > struct mmc_data *data = host->data;
> > @@ -1596,6 +1607,14 @@ static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
> > data->bytes_xfered = 0;
> > else
> > data->bytes_xfered = data->blksz * data->blocks;
> > +}
> > +EXPORT_SYMBOL_GPL(__sdhci_finish_data_common);
> > +
> > +static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
> > +{
> > + struct mmc_data *data = host->data;
> > +
> > + __sdhci_finish_data_common(host);
> >
> > /*
> > * Need to send CMD12 if -
> > @@ -1681,8 +1700,6 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> > sdhci_prepare_data(host, cmd);
> > }
> >
> > - sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
> > -
> > sdhci_set_transfer_mode(host, cmd);
> >
> > if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
> > @@ -1725,13 +1742,15 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> > if (host->use_external_dma)
> > sdhci_external_dma_pre_transfer(host, cmd);
> >
> > + sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
>
> If this move of "sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT)"
> is intentional, it should be a separate patch.
>
I will drop it and update in V7 version.
> > +
> > sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
> >
> > return true;
> > }
> >
> > -static bool sdhci_present_error(struct sdhci_host *host,
> > - struct mmc_command *cmd, bool present)
> > +bool sdhci_present_error(struct sdhci_host *host,
> > + struct mmc_command *cmd, bool present)
> > {
> > if (!present || host->flags & SDHCI_DEVICE_DEAD) {
> > cmd->error = -ENOMEDIUM;
> > @@ -1740,6 +1759,7 @@ static bool sdhci_present_error(struct sdhci_host *host,
> >
> > return false;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_present_error);
> >
> > static bool sdhci_send_command_retry(struct sdhci_host *host,
> > struct mmc_command *cmd,
> > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> > index c2f989dc2361..3b87fcae1294 100644
> > --- a/drivers/mmc/host/sdhci.h
> > +++ b/drivers/mmc/host/sdhci.h
> > @@ -831,6 +831,20 @@ static inline void sdhci_read_caps(struct sdhci_host *host)
> > __sdhci_read_caps(host, NULL, NULL, NULL);
> > }
> >
> > +bool sdhci_data_line_cmd(struct mmc_command *cmd);
> > +void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, unsigned long timeout);
> > +void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data);
> > +void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data);
> > +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA)
> > +int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd);
> > +void sdhci_external_dma_release(struct sdhci_host *host);
> > +void __sdhci_external_dma_prepare_data(struct sdhci_host *host, struct mmc_command *cmd);
> > +void sdhci_external_dma_pre_transfer(struct sdhci_host *host, struct mmc_command *cmd);
> > +#endif
>
> external_dma fns do not need export
>
I will update it in V7 version.
> > +void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> > +void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> > +void __sdhci_finish_data_common(struct sdhci_host *host);
> > +bool sdhci_present_error(struct sdhci_host *host, struct mmc_command *cmd, bool present);
> > u16 sdhci_calc_clk(struct sdhci_host *host, unsigned int clock,
> > unsigned int *actual_clock);
> > void sdhci_set_clock(struct sdhci_host *host, unsigned int clock);
>
Thanks, Victor Shih
Hi, Adrian
On Fri, Jan 6, 2023 at 5:30 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > This is a UHS-II version of sdhci's request() operation.
> > It handles UHS-II related command interrupts and errors.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/host/sdhci-uhs2.c | 223 +++++++++++++++++++++++++++++++++-
> > drivers/mmc/host/sdhci-uhs2.h | 3 +
> > drivers/mmc/host/sdhci.c | 111 +++++++++--------
> > drivers/mmc/host/sdhci.h | 6 +
> > 4 files changed, 294 insertions(+), 49 deletions(-)
> >
> > diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> > index 7a34f66c4215..769cf88b21cf 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.c
> > +++ b/drivers/mmc/host/sdhci-uhs2.c
> > @@ -16,6 +16,7 @@
> > #include <linux/bitfield.h>
> > #include <linux/ktime.h>
> > #include <linux/mmc/mmc.h>
> > +#include <linux/dmaengine.h>
>
> Looks like linux/dmaengine.h is not needed
>
I will drop it and update in V7 version.
> >
> > #include "sdhci.h"
> > #include "sdhci-uhs2.h"
> > @@ -794,6 +795,226 @@ static void sdhci_uhs2_finish_command(struct sdhci_host *host)
> > __sdhci_finish_mrq(host, cmd->mrq);
> > }
> >
> > +/*****************************************************************************\
> > + * *
> > + * Request done *
> > + * *
> > +\*****************************************************************************/
> > +
> > +static bool sdhci_uhs2_request_done(struct sdhci_host *host)
> > +{
> > + unsigned long flags;
> > + struct mmc_request *mrq;
> > + int i;
> > +
> > + spin_lock_irqsave(&host->lock, flags);
> > +
> > + for (i = 0; i < SDHCI_MAX_MRQS; i++) {
> > + mrq = host->mrqs_done[i];
> > + if (mrq)
> > + break;
> > + }
> > +
> > + if (!mrq) {
> > + spin_unlock_irqrestore(&host->lock, flags);
> > + return true;
> > + }
> > +
> > + /*
> > + * Always unmap the data buffers if they were mapped by
> > + * sdhci_prepare_data() whenever we finish with a request.
> > + * This avoids leaking DMA mappings on error.
> > + */
> > + if (host->flags & SDHCI_REQ_USE_DMA)
> > + sdhci_request_done_dma(host, mrq);
> > +
> > + /*
> > + * The controller needs a reset of internal state machines
> > + * upon error conditions.
> > + */
> > + if (sdhci_needs_reset(host, mrq)) {
> > + /*
> > + * Do not finish until command and data lines are available for
> > + * reset. Note there can only be one other mrq, so it cannot
> > + * also be in mrqs_done, otherwise host->cmd and host->data_cmd
> > + * would both be null.
> > + */
> > + if (host->cmd || host->data_cmd) {
> > + spin_unlock_irqrestore(&host->lock, flags);
> > + return true;
> > + }
> > +
> > + /* Some controllers need this kick or reset won't work here */
> > + if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET)
> > + /* This is to force an update */
> > + host->ops->set_clock(host, host->clock);
>
> Let's not support SDHCI_QUIRK_CLOCK_BEFORE_RESET
>
I will drop it and update in V7 version.
> > +
> > + sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
> > + host->pending_reset = false;
> > + }
> > +
> > + host->mrqs_done[i] = NULL;
> > +
> > + spin_unlock_irqrestore(&host->lock, flags);
> > +
> > + if (host->ops->request_done)
> > + host->ops->request_done(host, mrq);
> > + else
> > + mmc_request_done(host->mmc, mrq);
> > +
> > + return false;
> > +}
> > +
> > +static void sdhci_uhs2_complete_work(struct work_struct *work)
> > +{
> > + struct sdhci_host *host = container_of(work, struct sdhci_host,
> > + complete_work);
> > +
> > + if (!sdhci_uhs2_mode(host)) {
> > + sdhci_complete_work(work);
> > + return;
> > + }
> > +
> > + while (!sdhci_uhs2_request_done(host))
> > + ;
> > +}
> > +
> > +/*****************************************************************************\
> > + * *
> > + * Interrupt handling *
> > + * *
> > +\*****************************************************************************/
> > +
> > +static void __sdhci_uhs2_irq(struct sdhci_host *host, u32 uhs2mask)
> > +{
> > + struct mmc_command *cmd = host->cmd;
> > +
> > + DBG("*** %s got UHS2 error interrupt: 0x%08x\n",
> > + mmc_hostname(host->mmc), uhs2mask);
> > +
> > + if (uhs2mask & SDHCI_UHS2_INT_CMD_ERR_MASK) {
> > + if (!host->cmd) {
> > + pr_err("%s: Got cmd interrupt 0x%08x but no cmd.\n",
> > + mmc_hostname(host->mmc),
> > + (unsigned int)uhs2mask);
> > + sdhci_dumpregs(host);
> > + return;
> > + }
> > + host->cmd->error = -EILSEQ;
> > + if (uhs2mask & SDHCI_UHS2_INT_CMD_TIMEOUT)
> > + host->cmd->error = -ETIMEDOUT;
> > + }
> > +
> > + if (uhs2mask & SDHCI_UHS2_INT_DATA_ERR_MASK) {
> > + if (!host->data) {
> > + pr_err("%s: Got data interrupt 0x%08x but no data.\n",
> > + mmc_hostname(host->mmc),
> > + (unsigned int)uhs2mask);
> > + sdhci_dumpregs(host);
> > + return;
> > + }
> > +
> > + if (uhs2mask & SDHCI_UHS2_INT_DEADLOCK_TIMEOUT) {
> > + pr_err("%s: Got deadlock timeout interrupt 0x%08x\n",
> > + mmc_hostname(host->mmc),
> > + (unsigned int)uhs2mask);
> > + host->data->error = -ETIMEDOUT;
> > + } else if (uhs2mask & SDHCI_UHS2_INT_ADMA_ERROR) {
> > + pr_err("%s: ADMA error = 0x %x\n",
> > + mmc_hostname(host->mmc),
> > + sdhci_readb(host, SDHCI_ADMA_ERROR));
> > + host->data->error = -EIO;
> > + } else {
> > + host->data->error = -EILSEQ;
> > + }
> > + }
> > +
> > + if (host->data && host->data->error)
> > + sdhci_uhs2_finish_data(host);
> > + else
> > + sdhci_finish_mrq(host, cmd->mrq);
> > +}
> > +
> > +u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask)
> > +{
> > + u32 mask = intmask, uhs2mask;
> > +
> > + if (!(host->mmc->flags & MMC_UHS2_SUPPORT))
>
> Please use sdhci_uhs2_mode()
>
I will update it in V7 version.
> > + goto out;
> > +
> > + if (intmask & SDHCI_INT_ERROR) {
> > + uhs2mask = sdhci_readl(host, SDHCI_UHS2_INT_STATUS);
> > + if (!(uhs2mask & SDHCI_UHS2_INT_ERROR_MASK))
> > + goto cmd_irq;
> > +
> > + /* Clear error interrupts */
> > + sdhci_writel(host, uhs2mask & SDHCI_UHS2_INT_ERROR_MASK,
> > + SDHCI_UHS2_INT_STATUS);
> > +
> > + /* Handle error interrupts */
> > + __sdhci_uhs2_irq(host, uhs2mask);
> > +
> > + /* Caller, shdci_irq(), doesn't have to care UHS-2 errors */
>
> shdci -> sdhci
> care -> care about
>
I will update it in V7 version.
> > + intmask &= ~SDHCI_INT_ERROR;
> > + mask &= SDHCI_INT_ERROR;
> > + }
> > +
> > +cmd_irq:
> > + if (intmask & SDHCI_INT_CMD_MASK) {
> > + /* Clear command interrupt */
> > + sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK, SDHCI_INT_STATUS);
> > +
> > + /* Handle command interrupt */
> > + if (intmask & SDHCI_INT_RESPONSE)
> > + sdhci_uhs2_finish_command(host);
> > +
> > + /* Caller, shdci_irq(), doesn't have to care UHS-2 command */
>
> shdci -> sdhci
> care -> care about
> command -> commands
>
I will update it in V7 version.
> > + intmask &= ~SDHCI_INT_CMD_MASK;
> > + mask &= SDHCI_INT_CMD_MASK;
> > + }
> > +
> > + /* Clear already-handled interrupts. */
> > + sdhci_writel(host, mask, SDHCI_INT_STATUS);
> > +
> > +out:
> > + return intmask;
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_uhs2_irq);
> > +
> > +static irqreturn_t sdhci_uhs2_thread_irq(int irq, void *dev_id)
> > +{
> > + struct sdhci_host *host = dev_id;
> > + struct mmc_command *cmd;
> > + unsigned long flags;
> > + u32 isr;
> > +
> > + if (!sdhci_uhs2_mode(host))
> > + return sdhci_thread_irq(irq, dev_id);
> > +
> > + while (!sdhci_uhs2_request_done(host))
> > + ;
> > +
> > + spin_lock_irqsave(&host->lock, flags);
> > +
> > + isr = host->thread_isr;
> > + host->thread_isr = 0;
> > +
> > + cmd = host->deferred_cmd;
> > + if (cmd && !sdhci_uhs2_send_command_retry(host, cmd, flags))
> > + sdhci_finish_mrq(host, cmd->mrq);
> > +
> > + spin_unlock_irqrestore(&host->lock, flags);
> > +
> > + if (isr & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
> > + struct mmc_host *mmc = host->mmc;
> > +
> > + mmc->ops->card_event(mmc);
> > + mmc_detect_change(mmc, msecs_to_jiffies(200));
> > + }
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
> > {
> > struct sdhci_host *host = mmc_priv(mmc);
> > @@ -978,7 +1199,7 @@ static int sdhci_uhs2_do_detect_init(struct mmc_host *mmc)
> > }
> >
> > /* Init complete, do soft reset and enable UHS2 error irqs. */
> > - host->ops->uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
> > + sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_SD);
> > sdhci_uhs2_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_UHS2_INT_ERROR_MASK);
> > /*
> > * N.B SDHCI_INT_ENABLE and SDHCI_SIGNAL_ENABLE was cleared
> > diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
> > index 184fee80253c..64b7f356c9fc 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.h
> > +++ b/drivers/mmc/host/sdhci-uhs2.h
> > @@ -179,5 +179,8 @@ bool sdhci_uhs2_mode(struct sdhci_host *host);
> > void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
> > void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
> > void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
> > +void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq);
> > +int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq);
> > +u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask);
> >
> > #endif /* __SDHCI_UHS2_H */
> > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> > index c547fc3b8b47..6c67b79102eb 100644
> > --- a/drivers/mmc/host/sdhci.c
> > +++ b/drivers/mmc/host/sdhci.c
> > @@ -47,6 +47,8 @@
> > static unsigned int debug_quirks = 0;
> > static unsigned int debug_quirks2;
> >
> > +static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
> > +
> > void sdhci_dumpregs(struct sdhci_host *host)
> > {
> > SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n");
> > @@ -1256,11 +1258,12 @@ static int sdhci_external_dma_init(struct sdhci_host *host)
> > return ret;
> > }
> >
> > -static struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
> > - struct mmc_data *data)
> > +struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host,
> > + struct mmc_data *data)
> > {
> > return data->flags & MMC_DATA_WRITE ? host->tx_chan : host->rx_chan;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_external_dma_channel);
>
> Does not need export
>
I will update it in V7 version.
> >
> > int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd)
> > {
> > @@ -1509,7 +1512,7 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
> > sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
> > }
> >
> > -static bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
> > +bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
> > {
> > return (!(host->flags & SDHCI_DEVICE_DEAD) &&
> > ((mrq->cmd && mrq->cmd->error) ||
> > @@ -1517,8 +1520,9 @@ static bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq)
> > (mrq->data && mrq->data->stop && mrq->data->stop->error) ||
> > (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)));
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_needs_reset);
> >
> > -static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
> > +void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
> > {
> > int i;
> >
> > @@ -1538,6 +1542,7 @@ static void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq)
> >
> > WARN_ON(i >= SDHCI_MAX_MRQS);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_set_mrq_done);
> >
> > void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
> > {
> > @@ -3122,6 +3127,55 @@ static const struct mmc_host_ops sdhci_ops = {
> > * *
> > \*****************************************************************************/
> >
> > +void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq)
> > +{
> > + struct mmc_data *data = mrq->data;
> > +
> > + if (data && data->host_cookie == COOKIE_MAPPED) {
> > + if (host->bounce_buffer) {
> > + /*
> > + * On reads, copy the bounced data into the
> > + * sglist
> > + */
> > + if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
> > + unsigned int length = data->bytes_xfered;
> > +
> > + if (length > host->bounce_buffer_size) {
> > + pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
> > + mmc_hostname(host->mmc),
> > + host->bounce_buffer_size,
> > + data->bytes_xfered);
> > + /* Cap it down and continue */
> > + length = host->bounce_buffer_size;
> > + }
> > + dma_sync_single_for_cpu(
> > + host->mmc->parent,
> > + host->bounce_addr,
> > + host->bounce_buffer_size,
> > + DMA_FROM_DEVICE);
> > + sg_copy_from_buffer(data->sg,
> > + data->sg_len,
> > + host->bounce_buffer,
> > + length);
> > + } else {
> > + /* No copying, just switch ownership */
> > + dma_sync_single_for_cpu(
> > + host->mmc->parent,
> > + host->bounce_addr,
> > + host->bounce_buffer_size,
> > + mmc_get_dma_dir(data));
> > + }
> > + } else {
> > + /* Unmap the raw data */
> > + dma_unmap_sg(mmc_dev(host->mmc), data->sg,
> > + data->sg_len,
> > + mmc_get_dma_dir(data));
> > + }
> > + data->host_cookie = COOKIE_UNMAPPED;
> > + }
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_request_done_dma);
> > +
> > static bool sdhci_request_done(struct sdhci_host *host)
> > {
> > unsigned long flags;
> > @@ -3186,48 +3240,7 @@ static bool sdhci_request_done(struct sdhci_host *host)
> > sdhci_set_mrq_done(host, mrq);
> > }
> >
> > - if (data && data->host_cookie == COOKIE_MAPPED) {
> > - if (host->bounce_buffer) {
> > - /*
> > - * On reads, copy the bounced data into the
> > - * sglist
> > - */
> > - if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
> > - unsigned int length = data->bytes_xfered;
> > -
> > - if (length > host->bounce_buffer_size) {
> > - pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
> > - mmc_hostname(host->mmc),
> > - host->bounce_buffer_size,
> > - data->bytes_xfered);
> > - /* Cap it down and continue */
> > - length = host->bounce_buffer_size;
> > - }
> > - dma_sync_single_for_cpu(
> > - mmc_dev(host->mmc),
> > - host->bounce_addr,
> > - host->bounce_buffer_size,
> > - DMA_FROM_DEVICE);
> > - sg_copy_from_buffer(data->sg,
> > - data->sg_len,
> > - host->bounce_buffer,
> > - length);
> > - } else {
> > - /* No copying, just switch ownership */
> > - dma_sync_single_for_cpu(
> > - mmc_dev(host->mmc),
> > - host->bounce_addr,
> > - host->bounce_buffer_size,
> > - mmc_get_dma_dir(data));
> > - }
> > - } else {
> > - /* Unmap the raw data */
> > - dma_unmap_sg(mmc_dev(host->mmc), data->sg,
> > - data->sg_len,
> > - mmc_get_dma_dir(data));
> > - }
> > - data->host_cookie = COOKIE_UNMAPPED;
> > - }
> > + sdhci_request_done_dma(host, mrq);
> > }
> >
> > host->mrqs_done[i] = NULL;
> > @@ -3242,7 +3255,7 @@ static bool sdhci_request_done(struct sdhci_host *host)
> > return false;
> > }
> >
> > -static void sdhci_complete_work(struct work_struct *work)
> > +void sdhci_complete_work(struct work_struct *work)
> > {
> > struct sdhci_host *host = container_of(work, struct sdhci_host,
> > complete_work);
> > @@ -3250,6 +3263,7 @@ static void sdhci_complete_work(struct work_struct *work)
> > while (!sdhci_request_done(host))
> > ;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_complete_work);
> >
> > static void sdhci_timeout_timer(struct timer_list *t)
> > {
> > @@ -3705,7 +3719,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
> > return result;
> > }
> >
> > -static irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
> > +irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
> > {
> > struct sdhci_host *host = dev_id;
> > struct mmc_command *cmd;
> > @@ -3735,6 +3749,7 @@ static irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
> >
> > return IRQ_HANDLED;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_thread_irq);
> >
> > /*****************************************************************************\
> > * *
> > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> > index 3b87fcae1294..4979af7cabca 100644
> > --- a/drivers/mmc/host/sdhci.h
> > +++ b/drivers/mmc/host/sdhci.h
> > @@ -840,7 +840,10 @@ int sdhci_external_dma_setup(struct sdhci_host *host, struct mmc_command *cmd);
> > void sdhci_external_dma_release(struct sdhci_host *host);
> > void __sdhci_external_dma_prepare_data(struct sdhci_host *host, struct mmc_command *cmd);
> > void sdhci_external_dma_pre_transfer(struct sdhci_host *host, struct mmc_command *cmd);
> > +struct dma_chan *sdhci_external_dma_channel(struct sdhci_host *host, struct mmc_data *data);
>
> Does not need export
>
I will update it in V7 version.
> > #endif
> > +bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq);
> > +void sdhci_set_mrq_done(struct sdhci_host *host, struct mmc_request *mrq);
> > void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> > void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq);
> > void __sdhci_finish_data_common(struct sdhci_host *host);
> > @@ -870,6 +873,9 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
> > int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> > struct mmc_ios *ios);
> > void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
> > +void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq);
> > +void sdhci_complete_work(struct work_struct *work);
> > +irqreturn_t sdhci_thread_irq(int irq, void *dev_id);
> > void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
> > dma_addr_t addr, int len, unsigned int cmd);
> >
>
Thanks, Victor Shih
Hi, Adrian
On Fri, Jan 6, 2023 at 5:30 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > This is a UHS-II version of sdhci's add_host/remove_host operation.
> > Any sdhci drivers which are capable of handling UHS-II cards must
> > call those functions instead of the corresponding sdhci's.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/host/sdhci-uhs2.c | 171 ++++++++++++++++++++++++++++++++++
> > drivers/mmc/host/sdhci-uhs2.h | 2 +
> > drivers/mmc/host/sdhci.c | 28 ++++--
> > drivers/mmc/host/sdhci.h | 12 +++
> > 4 files changed, 204 insertions(+), 9 deletions(-)
> >
> > diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> > index 769cf88b21cf..5349173b4f90 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.c
> > +++ b/drivers/mmc/host/sdhci-uhs2.c
> > @@ -17,6 +17,7 @@
> > #include <linux/ktime.h>
> > #include <linux/mmc/mmc.h>
> > #include <linux/dmaengine.h>
> > +#include <linux/regulator/consumer.h>
> >
> > #include "sdhci.h"
> > #include "sdhci-uhs2.h"
> > @@ -1015,6 +1016,176 @@ static irqreturn_t sdhci_uhs2_thread_irq(int irq, void *dev_id)
> > return IRQ_HANDLED;
> > }
> >
> > +/*****************************************************************************\
> > + *
> > + * Device allocation/registration *
> > + * *
> > +\*****************************************************************************/
> > +
> > +static int __sdhci_uhs2_add_host_v4(struct sdhci_host *host, u32 caps1)
> > +{
> > + struct mmc_host *mmc;
> > + u32 max_current_caps2;
> > +
> > + if (host->version < SDHCI_SPEC_400)
> > + return 0;
> > +
> > + mmc = host->mmc;
> > +
> > + /* Support UHS2 */
> > + if (caps1 & SDHCI_SUPPORT_UHS2)
> > + mmc->caps2 |= MMC_CAP2_SD_UHS2;
> > +
> > + max_current_caps2 = sdhci_readl(host, SDHCI_MAX_CURRENT_1);
> > +
> > + if ((caps1 & SDHCI_CAN_VDD2_180) &&
> > + !max_current_caps2 &&
> > + !IS_ERR(mmc->supply.vmmc2)) {
> > + /* UHS2 - VDD2 */
> > + int curr = regulator_get_current_limit(mmc->supply.vmmc2);
> > +
> > + if (curr > 0) {
> > + /* convert to SDHCI_MAX_CURRENT format */
> > + curr = curr / 1000; /* convert to mA */
> > + curr = curr / SDHCI_MAX_CURRENT_MULTIPLIER;
> > + curr = min_t(u32, curr, SDHCI_MAX_CURRENT_LIMIT);
> > + max_current_caps2 = curr;
> > + }
> > + }
> > +
> > + if (caps1 & SDHCI_CAN_VDD2_180) {
> > + mmc->ocr_avail_uhs2 |= MMC_VDD2_165_195;
> > + /*
> > + * UHS2 doesn't require this. Only UHS-I bus needs to set
> > + * max current.
> > + */
> > + mmc->max_current_180_vdd2 = (max_current_caps2 &
> > + SDHCI_MAX_CURRENT_VDD2_180_MASK) *
> > + SDHCI_MAX_CURRENT_MULTIPLIER;
> > + } else {
> > + mmc->caps2 &= ~MMC_CAP2_SD_UHS2;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int sdhci_uhs2_host_ops_init(struct sdhci_host *host);
> > +
> > +static int __sdhci_uhs2_add_host(struct sdhci_host *host)
> > +{
> > + unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
> > + struct mmc_host *mmc = host->mmc;
> > + int ret;
> > +
> > + if ((mmc->caps2 & MMC_CAP2_CQE) &&
> > + (host->quirks & SDHCI_QUIRK_BROKEN_CQE)) {
> > + mmc->caps2 &= ~MMC_CAP2_CQE;
> > + mmc->cqe_ops = NULL;
> > + }
> > +
> > + host->complete_wq = alloc_workqueue("sdhci", flags, 0);
> > + if (!host->complete_wq)
> > + return -ENOMEM;
> > +
> > + INIT_WORK(&host->complete_work, host->complete_work_fn);
> > +
> > + timer_setup(&host->timer, sdhci_timeout_timer, 0);
> > + timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
> > +
> > + init_waitqueue_head(&host->buf_ready_int);
> > +
> > + sdhci_init(host, 0);
> > +
> > + ret = request_threaded_irq(host->irq, sdhci_irq,
> > + host->thread_irq_fn,
> > + IRQF_SHARED, mmc_hostname(mmc), host);
> > + if (ret) {
> > + pr_err("%s: Failed to request IRQ %d: %d\n",
> > + mmc_hostname(mmc), host->irq, ret);
> > + goto unwq;
> > + }
> > +
> > + ret = mmc_add_host(mmc);
> > + if (ret)
> > + return 1;
> > +
> > + pr_info("%s: SDHCI controller on %s [%s] using %s\n",
> > + mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
> > + host->use_external_dma ? "External DMA" :
> > + (host->flags & SDHCI_USE_ADMA) ?
> > + (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" :
> > + (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");
> > +
> > + sdhci_enable_card_detection(host);
> > +
> > + return 0;
> > +
> > +unwq:
> > + destroy_workqueue(host->complete_wq);
> > +
> > + return ret;
> > +}
> > +
> > +static void __sdhci_uhs2_remove_host(struct sdhci_host *host, int dead)
> > +{
> > + if (!sdhci_uhs2_mode(host))
> > + return;
> > +
> > + if (!dead)
> > + sdhci_uhs2_reset(host, SDHCI_UHS2_SW_RESET_FULL);
> > +}
> > +
> > +int sdhci_uhs2_add_host(struct sdhci_host *host)
> > +{
> > + struct mmc_host *mmc = host->mmc;
> > + int ret;
> > +
> > + ret = sdhci_setup_host(host);
> > + if (ret)
> > + return ret;
> > +
> > + if (host->version >= SDHCI_SPEC_400) {
> > + ret = __sdhci_uhs2_add_host_v4(host, host->caps1);
> > + if (ret)
> > + goto cleanup;
> > + }
> > +
> > + if ((mmc->caps2 & MMC_CAP2_SD_UHS2) && !host->v4_mode)
> > + /* host doesn't want to enable UHS2 support */
> > + /* FIXME: Do we have to do some cleanup here? */
> > + mmc->caps2 &= ~MMC_CAP2_SD_UHS2;
> > +
> > + /* overwrite ops */
> > + if (mmc->caps2 & MMC_CAP2_SD_UHS2)
> > + sdhci_uhs2_host_ops_init(host);
> > +
> > + host->complete_work_fn = sdhci_uhs2_complete_work;
> > + host->thread_irq_fn = sdhci_uhs2_thread_irq;
> > +
> > + ret = __sdhci_uhs2_add_host(host);
>
> Please just use __sdhci_add_host() and __sdhci_uhs2_add_host()
> is not needed. i.e.
>
> /* LED support not implemented for UHS2 */
> host->quirks |= SDHCI_QUIRK_NO_LED;
>
> ret = __sdhci_add_host(host);
>
I will update it in V7 version.
> > + if (ret)
> > + goto cleanup2;
> > +
> > + return 0;
> > +
> > +cleanup2:
> > + if (host->version >= SDHCI_SPEC_400)
> > + __sdhci_uhs2_remove_host(host, 0);
> > +cleanup:
> > + sdhci_cleanup_host(host);
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_uhs2_add_host);
> > +
> > +void sdhci_uhs2_remove_host(struct sdhci_host *host, int dead)
> > +{
> > + __sdhci_uhs2_remove_host(host, dead);
> > +
> > + sdhci_remove_host(host, dead);
> > +}
> > +EXPORT_SYMBOL_GPL(sdhci_uhs2_remove_host);
> > +
> > void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq)
> > {
> > struct sdhci_host *host = mmc_priv(mmc);
> > diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
> > index 64b7f356c9fc..b2048c48d056 100644
> > --- a/drivers/mmc/host/sdhci-uhs2.h
> > +++ b/drivers/mmc/host/sdhci-uhs2.h
> > @@ -182,5 +182,7 @@ void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
> > void sdhci_uhs2_request(struct mmc_host *mmc, struct mmc_request *mrq);
> > int sdhci_uhs2_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq);
> > u32 sdhci_uhs2_irq(struct sdhci_host *host, u32 intmask);
> > +int sdhci_uhs2_add_host(struct sdhci_host *host);
> > +void sdhci_uhs2_remove_host(struct sdhci_host *host, int dead);
> >
> > #endif /* __SDHCI_UHS2_H */
> > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> > index 6c67b79102eb..82ad35f4d1b4 100644
> > --- a/drivers/mmc/host/sdhci.c
> > +++ b/drivers/mmc/host/sdhci.c
> > @@ -175,10 +175,11 @@ static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
> > sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
> > }
> >
> > -static void sdhci_enable_card_detection(struct sdhci_host *host)
> > +void sdhci_enable_card_detection(struct sdhci_host *host)
> > {
> > sdhci_set_card_detection(host, true);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_enable_card_detection);
>
> sdhci_enable_card_detection() does not need to be exported
> when __sdhci_uhs2_add_host() is not needed anymore
>
I will update it in V7 version.
> >
> > static void sdhci_disable_card_detection(struct sdhci_host *host)
> > {
> > @@ -369,7 +370,7 @@ static void sdhci_config_dma(struct sdhci_host *host)
> > sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
> > }
> >
> > -static void sdhci_init(struct sdhci_host *host, int soft)
> > +void sdhci_init(struct sdhci_host *host, int soft)
> > {
> > struct mmc_host *mmc = host->mmc;
> > unsigned long flags;
> > @@ -395,6 +396,7 @@ static void sdhci_init(struct sdhci_host *host, int soft)
> > mmc->ops->set_ios(mmc, &mmc->ios);
> > }
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_init);
>
> Does not need to be exported when __sdhci_uhs2_add_host()
> is not needed anymore
>
I will update it in V7 version.
> >
> > static void sdhci_reinit(struct sdhci_host *host)
> > {
> > @@ -458,7 +460,7 @@ static void sdhci_led_control(struct led_classdev *led,
> > spin_unlock_irqrestore(&host->lock, flags);
> > }
> >
> > -static int sdhci_led_register(struct sdhci_host *host)
> > +int sdhci_led_register(struct sdhci_host *host)
> > {
> > struct mmc_host *mmc = host->mmc;
> >
> > @@ -475,14 +477,16 @@ static int sdhci_led_register(struct sdhci_host *host)
> >
> > return led_classdev_register(mmc_dev(mmc), &host->led);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_led_register);
>
> sdhci_led_register does not need export
>
I will update it in V7 version.
> >
> > -static void sdhci_led_unregister(struct sdhci_host *host)
> > +void sdhci_led_unregister(struct sdhci_host *host)
> > {
> > if (host->quirks & SDHCI_QUIRK_NO_LED)
> > return;
> >
> > led_classdev_unregister(&host->led);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_led_unregister);
>
> sdhci_led_unregister does not need export
>
I will update it in V7 version.
> >
> > static inline void sdhci_led_activate(struct sdhci_host *host)
> > {
> > @@ -3265,7 +3269,7 @@ void sdhci_complete_work(struct work_struct *work)
> > }
> > EXPORT_SYMBOL_GPL(sdhci_complete_work);
> >
> > -static void sdhci_timeout_timer(struct timer_list *t)
> > +void sdhci_timeout_timer(struct timer_list *t)
> > {
> > struct sdhci_host *host;
> > unsigned long flags;
> > @@ -3286,8 +3290,9 @@ static void sdhci_timeout_timer(struct timer_list *t)
> >
> > spin_unlock_irqrestore(&host->lock, flags);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_timeout_timer);
>
> sdhci_timeout_timer() does not need export
> when __sdhci_uhs2_add_host() is not needed anymore
>
I will update it in V7 version.
> >
> > -static void sdhci_timeout_data_timer(struct timer_list *t)
> > +void sdhci_timeout_data_timer(struct timer_list *t)
> > {
> > struct sdhci_host *host;
> > unsigned long flags;
> > @@ -3318,6 +3323,7 @@ static void sdhci_timeout_data_timer(struct timer_list *t)
> >
> > spin_unlock_irqrestore(&host->lock, flags);
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_timeout_data_timer);
>
> sdhci_timeout_data_timer() does not need export
> when __sdhci_uhs2_add_host() is not needed anymore
>
I will update it in V7 version.
> >
> > /*****************************************************************************\
> > * *
> > @@ -3577,7 +3583,7 @@ static inline bool sdhci_defer_done(struct sdhci_host *host,
> > data->host_cookie == COOKIE_MAPPED);
> > }
> >
> > -static irqreturn_t sdhci_irq(int irq, void *dev_id)
> > +irqreturn_t sdhci_irq(int irq, void *dev_id)
> > {
> > struct mmc_request *mrqs_done[SDHCI_MAX_MRQS] = {0};
> > irqreturn_t result = IRQ_NONE;
> > @@ -3718,6 +3724,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
> >
> > return result;
> > }
> > +EXPORT_SYMBOL_GPL(sdhci_irq);
>
> sdhci_irq() does not need export
> when __sdhci_uhs2_add_host() is not needed anymore
>
I will update it in V7 version.
> >
> > irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
> > {
> > @@ -4122,6 +4129,9 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,
> >
> > host->max_timeout_count = 0xE;
> >
> > + host->complete_work_fn = sdhci_complete_work;
> > + host->thread_irq_fn = sdhci_thread_irq;
> > +
> > return host;
> > }
> >
> > @@ -4874,7 +4884,7 @@ int __sdhci_add_host(struct sdhci_host *host)
> > if (!host->complete_wq)
> > return -ENOMEM;
> >
> > - INIT_WORK(&host->complete_work, sdhci_complete_work);
> > + INIT_WORK(&host->complete_work, host->complete_work_fn);
> >
> > timer_setup(&host->timer, sdhci_timeout_timer, 0);
> > timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
> > @@ -4883,7 +4893,7 @@ int __sdhci_add_host(struct sdhci_host *host)
> >
> > sdhci_init(host, 0);
> >
> > - ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
> > + ret = request_threaded_irq(host->irq, sdhci_irq, host->thread_irq_fn,
> > IRQF_SHARED, mmc_hostname(mmc), host);
> > if (ret) {
> > pr_err("%s: Failed to request IRQ %d: %d\n",
> > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> > index 4979af7cabca..5f090b5ecc61 100644
> > --- a/drivers/mmc/host/sdhci.h
> > +++ b/drivers/mmc/host/sdhci.h
> > @@ -629,6 +629,9 @@ struct sdhci_host {
> > struct timer_list timer; /* Timer for timeouts */
> > struct timer_list data_timer; /* Timer for data timeouts */
> >
> > + void (*complete_work_fn)(struct work_struct *work);
> > + irqreturn_t (*thread_irq_fn)(int irq, void *dev_id);
> > +
> > #if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA)
> > struct dma_chan *rx_chan;
> > struct dma_chan *tx_chan;
> > @@ -832,6 +835,12 @@ static inline void sdhci_read_caps(struct sdhci_host *host)
> > }
> >
> > bool sdhci_data_line_cmd(struct mmc_command *cmd);
> > +void sdhci_enable_card_detection(struct sdhci_host *host);
> > +void sdhci_init(struct sdhci_host *host, int soft);
> > +#if IS_REACHABLE(CONFIG_LEDS_CLASS)
> > +int sdhci_led_register(struct sdhci_host *host);
> > +void sdhci_led_unregister(struct sdhci_host *host);
> > +#endif
>
> Export of sdhci_led_register and sdhci_led_unregister
> is not needed
>
I will update it in V7 version.
> > void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, unsigned long timeout);
> > void sdhci_initialize_data(struct sdhci_host *host, struct mmc_data *data);
> > void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data);
> > @@ -875,6 +884,9 @@ int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> > void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable);
> > void sdhci_request_done_dma(struct sdhci_host *host, struct mmc_request *mrq);
> > void sdhci_complete_work(struct work_struct *work);
> > +void sdhci_timeout_timer(struct timer_list *t);
> > +void sdhci_timeout_data_timer(struct timer_list *t);
>
> sdhci_timeout_timer() and sdhci_timeout_data_timer()
> do not need export when __sdhci_uhs2_add_host()
> is not needed anymore
>
I will update it in V7 version.
> > +irqreturn_t sdhci_irq(int irq, void *dev_id);
>
> sdhci_irq() does not need export
> when __sdhci_uhs2_add_host() is not needed anymore
>
I will update it in V7 version.
> > irqreturn_t sdhci_thread_irq(int irq, void *dev_id);
> > void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
> > dma_addr_t addr, int len, unsigned int cmd);
>
Thanks, Victor Shih
Hi, Adrian
On Fri, Jan 6, 2023 at 5:31 AM Adrian Hunter <[email protected]> wrote:
>
> On 13/12/22 11:00, Victor Shih wrote:
> > From: Ben Chuang <[email protected]>
> >
> > This "post" hook for mmc_attach_sd(), uhs2_post_attach_sd, will be required
> > to enable UHS-II support, at least, on GL9755.
> >
> > Signed-off-by: Ben Chuang <[email protected]>
> > Signed-off-by: AKASHI Takahiro <[email protected]>
> > ---
> > drivers/mmc/host/sdhci.h | 1 +
> > 1 file changed, 1 insertion(+)
> >
> > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> > index e5f64b38e339..4aac22b58d79 100644
> > --- a/drivers/mmc/host/sdhci.h
> > +++ b/drivers/mmc/host/sdhci.h
> > @@ -728,6 +728,7 @@ struct sdhci_ops {
> > void (*dump_vendor_regs)(struct sdhci_host *host);
> > void (*dump_uhs2_regs)(struct sdhci_host *host);
> > void (*uhs2_pre_detect_init)(struct sdhci_host *host);
> > + void (*uhs2_post_attach_sd)(struct sdhci_host *host);
>
> This gets called already in "mmc: sdhci-uhs2: add uhs2_control()
> to initialise the interface"
>
> Either move that line to this patch or vice versa.
>
> Please check compiling after each patch is applied.
>
I will update it in V7 version.
> > };
> >
> > #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
>
Thanks, Victor Shih
Hi, Adrian
On Mon, Jan 30, 2023 at 9:16 PM Adrian Hunter <[email protected]> wrote:
>
> On 26/01/23 12:58, Victor Shih wrote:
> > Hi, Adrian
> >
> > On Fri, Jan 6, 2023 at 5:28 AM Adrian Hunter <[email protected]> wrote:
> >>
> >> On 13/12/22 11:00, Victor Shih wrote:
> >>> This is a sdhci version of mmc's set_ios operation.
> >>> It covers both UHS-I and UHS-II.
> >>>
> >>> Signed-off-by: Ben Chuang <[email protected]>
> >>> Signed-off-by: AKASHI Takahiro <[email protected]>
> >>> Signed-off-by: Victor Shih <[email protected]>
> >>> ---
> >>> drivers/mmc/host/sdhci-uhs2.c | 96 +++++++++++++++++++++++++++++++++++
> >>> drivers/mmc/host/sdhci-uhs2.h | 1 +
> >>> drivers/mmc/host/sdhci.c | 53 +++++++++++--------
> >>> drivers/mmc/host/sdhci.h | 2 +
> >>> 4 files changed, 131 insertions(+), 21 deletions(-)
> >>>
> >>> diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c
> >>> index 00b1b69b49ea..3d52d35a91a5 100644
> >>> --- a/drivers/mmc/host/sdhci-uhs2.c
> >>> +++ b/drivers/mmc/host/sdhci-uhs2.c
> >>> @@ -213,6 +213,68 @@ void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
> >>> }
> >>> EXPORT_SYMBOL_GPL(sdhci_uhs2_set_timeout);
> >>>
> >>> +/**
> >>> + * sdhci_uhs2_clear_set_irqs - set Error Interrupt Status Enable register
> >>> + * @host: SDHCI host
> >>> + * @clear: bit-wise clear mask
> >>> + * @set: bit-wise set mask
> >>> + *
> >>> + * Set/unset bits in UHS-II Error Interrupt Status Enable register
> >>> + */
> >>> +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
> >>> +{
> >>> + u32 ier;
> >>> +
> >>> + ier = sdhci_readl(host, SDHCI_UHS2_INT_STATUS_ENABLE);
> >>> + ier &= ~clear;
> >>> + ier |= set;
> >>> + sdhci_writel(host, ier, SDHCI_UHS2_INT_STATUS_ENABLE);
> >>> + sdhci_writel(host, ier, SDHCI_UHS2_INT_SIGNAL_ENABLE);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(sdhci_uhs2_clear_set_irqs);
> >>> +
> >>> +static void __sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> >>> +{
> >>> + struct sdhci_host *host = mmc_priv(mmc);
> >>> + u8 cmd_res, dead_lock;
> >>> + u16 ctrl_2;
> >>> +
> >>> + /* UHS2 Timeout Control */
> >>> + sdhci_calc_timeout_uhs2(host, &cmd_res, &dead_lock);
> >>> +
> >>> + /* change to use calculate value */
> >>> + cmd_res |= FIELD_PREP(SDHCI_UHS2_TIMER_CTRL_DEADLOCK_MASK, dead_lock);
> >>> +
> >>> + sdhci_uhs2_clear_set_irqs(host,
> >>> + SDHCI_UHS2_INT_CMD_TIMEOUT |
> >>> + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT,
> >>> + 0);
> >>> + sdhci_writeb(host, cmd_res, SDHCI_UHS2_TIMER_CTRL);
> >>> + sdhci_uhs2_clear_set_irqs(host, 0,
> >>> + SDHCI_UHS2_INT_CMD_TIMEOUT |
> >>> + SDHCI_UHS2_INT_DEADLOCK_TIMEOUT);
> >>> +
> >>> + /* UHS2 timing */
> >>
> >> Please extend comment to include:
> >>
> >> Note, UHS2 timing is disabled when powering off
> >>
I will update it in V7 version.
> >>> + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> >>> + if (ios->timing == MMC_TIMING_SD_UHS2)
> >>> + ctrl_2 |= SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE;
> >>> + else
> >>> + ctrl_2 &= ~(SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE);
> >>> + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> >>> +
> >>> + if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
> >>> + sdhci_enable_preset_value(host, true);
> >>> +
> >>> + if (host->ops->set_power)
> >>> + host->ops->set_power(host, ios->power_mode, ios->vdd);
> >>> + else
> >>> + sdhci_uhs2_set_power(host, ios->power_mode, ios->vdd);
> >>
> >> sdhci_set_ios_common() already set the power. Are both needed?
> >>
I will move the set_power function in the sdhci_set_ios_common to the
sdhci_set_ios and update it in V7 version.
> >>> + udelay(100);
> >>
> >> Please add a comment for why this delay is here.
> >>
I will drop it and update in V7 version.
> >>> +
> >>> + host->timing = ios->timing;
> >>
> >> Please move this up to where the timing change is.
> >>
> >
> > The ios->timing was changed in sd_uhs2_power_up function and not
> > modified in this function(__sdhci_uhs2_set_ios), therefore would you
> > want me to move this to the sd_uhs2_power_up function?
>
> No
>
> >
> > As I know the ios->timing is used in sdhci_set_clock function, setting
> > the host->timing before call the sdhci_set_clock function ensures that
> > the host->timing used in sdhci_set_clock function is the most recent
> > value. What do you think about this?
>
> I just meant move it below the timing chunk i.e.
>
> /* UHS2 timing. Note, UHS2 timing is disabled when powering off */
> ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> if (ios->timing == MMC_TIMING_SD_UHS2)
> ctrl_2 |= SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE;
> else
> ctrl_2 &= ~(SDHCI_CTRL_UHS2 | SDHCI_CTRL_UHS2_ENABLE);
> sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> host->timing = ios->timing;
>
I will update it in V7 version.
> >
> >>> + sdhci_set_clock(host, host->clock);
> >>> +}
> >>> +
> >>> /*****************************************************************************\
> >>> * *
> >>> * MMC callbacks *
> >>> @@ -234,6 +296,39 @@ static int sdhci_uhs2_start_signal_voltage_switch(struct mmc_host *mmc,
> >>> return sdhci_start_signal_voltage_switch(mmc, ios);
> >>> }
> >>>
> >>> +int sdhci_uhs2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> >>
> >> Should be static
> >>
I will update it in V7 version.
> >>> +{
> >>> + struct sdhci_host *host = mmc_priv(mmc);
> >>> +
> >>> + if (!(host->version >= SDHCI_SPEC_400) ||
> >>> + !(host->mmc->flags & MMC_UHS2_SUPPORT &&
> >>> + host->mmc->caps2 & MMC_CAP2_SD_UHS2)) {
> >>
> >> This can be just:
> >>
> >> if (!sdhci_uhs2_mode(host)) {
> >>
> >> Not sure if this is actually possible?
> >>
I will update it in V7 version.
> >>> + sdhci_set_ios(mmc, ios);
> >>> + return 0;
> >>> + }
> >>> +
> >>> + if (ios->power_mode == MMC_POWER_UNDEFINED)
> >>> + return 0;
> >>> +
> >>> + if (host->flags & SDHCI_DEVICE_DEAD) {
> >>> + if (!IS_ERR(mmc->supply.vmmc) &&
> >>> + ios->power_mode == MMC_POWER_OFF)
> >>> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> >>> + if (!IS_ERR_OR_NULL(mmc->supply.vmmc2) &&
> >>> + ios->power_mode == MMC_POWER_OFF)
> >>> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
> >>
> >> This can be just:
> >>
> >> if (ios->power_mode == MMC_POWER_OFF) {
> >> mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> >> mmc_opt_regulator_set_ocr(mmc, mmc->supply.vmmc2, 0);
> >> }
> >>
I will update it in V7 version.
> >>> + return -1;
> >>> + }
> >>> +
> >>> + host->timing = ios->timing;
> >>
> >> __sdhci_uhs2_set_ios() does this so it is not needed here.
> >>
I will update it in V7 version.
> >>> +
> >>> + sdhci_set_ios_common(mmc, ios);
> >>> +
> >>> + __sdhci_uhs2_set_ios(mmc, ios);
> >>> +
> >>> + return 0;
> >>> +}
> >>> +
> >>> /*****************************************************************************\
> >>> * *
> >>> * Driver init/exit *
> >>> @@ -244,6 +339,7 @@ static int sdhci_uhs2_host_ops_init(struct sdhci_host *host)
> >>> {
> >>> host->mmc_host_ops.start_signal_voltage_switch =
> >>> sdhci_uhs2_start_signal_voltage_switch;
> >>> + host->mmc_host_ops.uhs2_set_ios = sdhci_uhs2_set_ios;
> >>>
> >>> return 0;
> >>> }
> >>> diff --git a/drivers/mmc/host/sdhci-uhs2.h b/drivers/mmc/host/sdhci-uhs2.h
> >>> index a58ef19c08aa..184fee80253c 100644
> >>> --- a/drivers/mmc/host/sdhci-uhs2.h
> >>> +++ b/drivers/mmc/host/sdhci-uhs2.h
> >>> @@ -178,5 +178,6 @@ void sdhci_uhs2_dump_regs(struct sdhci_host *host);
> >>> bool sdhci_uhs2_mode(struct sdhci_host *host);
> >>> void sdhci_uhs2_reset(struct sdhci_host *host, u16 mask);
> >>> void sdhci_uhs2_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
> >>> +void sdhci_uhs2_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set);
> >>>
> >>> #endif /* __SDHCI_UHS2_H */
> >>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> >>> index 99633a3ef549..49bbdc155b2b 100644
> >>> --- a/drivers/mmc/host/sdhci.c
> >>> +++ b/drivers/mmc/host/sdhci.c
> >>> @@ -47,8 +47,6 @@
> >>> static unsigned int debug_quirks = 0;
> >>> static unsigned int debug_quirks2;
> >>>
> >>> -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
> >>> -
> >>> static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd);
> >>>
> >>> void sdhci_dumpregs(struct sdhci_host *host)
> >>> @@ -1877,6 +1875,9 @@ static u16 sdhci_get_preset_value(struct sdhci_host *host)
> >>> case MMC_TIMING_MMC_HS400:
> >>> preset = sdhci_readw(host, SDHCI_PRESET_FOR_HS400);
> >>> break;
> >>> + case MMC_TIMING_SD_UHS2:
> >>> + preset = sdhci_readw(host, SDHCI_PRESET_FOR_UHS2);
> >>> + break;
> >>> default:
> >>> pr_warn("%s: Invalid UHS-I mode selected\n",
> >>> mmc_hostname(host->mmc));
> >>> @@ -2325,24 +2326,9 @@ static bool sdhci_presetable_values_change(struct sdhci_host *host, struct mmc_i
> >>> (sdhci_preset_needed(host, ios->timing) || host->drv_type != ios->drv_type);
> >>> }
> >>>
> >>> -void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> >>> +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios)
> >>> {
> >>> struct sdhci_host *host = mmc_priv(mmc);
> >>> - bool reinit_uhs = host->reinit_uhs;
> >>> - bool turning_on_clk = false;
> >>> - u8 ctrl;
> >>> -
> >>> - host->reinit_uhs = false;
> >>> -
> >>> - if (ios->power_mode == MMC_POWER_UNDEFINED)
> >>> - return;
> >>> -
> >>> - if (host->flags & SDHCI_DEVICE_DEAD) {
> >>> - if (!IS_ERR(mmc->supply.vmmc) &&
> >>> - ios->power_mode == MMC_POWER_OFF)
> >>> - mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> >>> - return;
> >>> - }
> >>>
> >>> /*
> >>> * Reset the chip on each power off.
> >>> @@ -2359,8 +2345,6 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> >>> sdhci_enable_preset_value(host, false);
> >>>
> >>> if (!ios->clock || ios->clock != host->clock) {
> >>> - turning_on_clk = ios->clock && !host->clock;
> >>> -
> >>> host->ops->set_clock(host, ios->clock);
> >>> host->clock = ios->clock;
> >>>
> >>> @@ -2381,6 +2365,32 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> >>> host->ops->set_power(host, ios->power_mode, ios->vdd);
> >>> else
> >>> sdhci_set_power(host, ios->power_mode, ios->vdd);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(sdhci_set_ios_common);
> >>> +
> >>> +void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> >>> +{
> >>> + struct sdhci_host *host = mmc_priv(mmc);
> >>> + bool reinit_uhs = host->reinit_uhs;
> >>> + bool turning_on_clk = false;
> >>> + u8 ctrl;
> >>> +
> >>> + host->reinit_uhs = false;
> >>> +
> >>> + if (ios->power_mode == MMC_POWER_UNDEFINED)
> >>> + return;
> >>> +
> >>> + if (host->flags & SDHCI_DEVICE_DEAD) {
> >>> + if (!IS_ERR(mmc->supply.vmmc) &&
> >>> + ios->power_mode == MMC_POWER_OFF)
> >>> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> >>> + return;
> >>> + }
> >>> +
> >>> + sdhci_set_ios_common(mmc, ios);
> >>> +
> >>> + if (!ios->clock || ios->clock != host->clock)
> >>> + turning_on_clk = ios->clock && !host->clock;
> >>>
> >>> if (host->ops->platform_send_init_74_clocks)
> >>> host->ops->platform_send_init_74_clocks(host, ios->power_mode);
> >>> @@ -2959,7 +2969,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
> >>> }
> >>> EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
> >>>
> >>> -static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> >>> +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> >>> {
> >>> /* Host Controller v3.00 defines preset value registers */
> >>> if (host->version < SDHCI_SPEC_300)
> >>> @@ -2987,6 +2997,7 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable)
> >>> host->preset_enabled = enable;
> >>> }
> >>> }
> >>> +EXPORT_SYMBOL_GPL(sdhci_enable_preset_value);
> >>>
> >>> static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
> >>> int err)
> >>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> >>> index df7fa0c0ebf8..c2f989dc2361 100644
> >>> --- a/drivers/mmc/host/sdhci.h
> >>> +++ b/drivers/mmc/host/sdhci.h
> >>> @@ -850,6 +850,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
> >>> void sdhci_reset(struct sdhci_host *host, u8 mask);
> >>> void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
> >>> int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
> >>> +void sdhci_enable_preset_value(struct sdhci_host *host, bool enable);
> >>> +void sdhci_set_ios_common(struct mmc_host *mmc, struct mmc_ios *ios);
> >>> void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
> >>> int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> >>> struct mmc_ios *ios);
> >>
> >
> > Thanks, Victor Shih
>
Thanks, Victor Shih
Hi, Ulf
On Wed, Feb 8, 2023 at 11:31 PM Ulf Hansson <[email protected]> wrote:
>
> "On Tue, 13 Dec 2022 at 10:01, Victor Shih <[email protected]> wrote:
> >
> > Embed UHS-II access/control functionality into the MMC request
> > processing flow.
> >
> > Signed-off-by: Ulf Hansson <[email protected]>
> > Signed-off-by: Jason Lai <[email protected]>
> > Signed-off-by: Victor Shih <[email protected]>
> > ---
> > drivers/mmc/core/block.c | 6 +-
> > drivers/mmc/core/core.c | 20 +
> > drivers/mmc/core/mmc_ops.c | 25 +-
> > drivers/mmc/core/mmc_ops.h | 1 +
> > drivers/mmc/core/sd.c | 11 +-
> > drivers/mmc/core/sd.h | 3 +
> > drivers/mmc/core/sd_ops.c | 13 +
> > drivers/mmc/core/sd_ops.h | 3 +
> > drivers/mmc/core/sd_uhs2.c | 1171 +++++++++++++++++++++++++++++++++++-
> > 9 files changed, 1206 insertions(+), 47 deletions(-)
> >
> > diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
> > index 20da7ed43e6d..d3e8ec43cdd5 100644
> > --- a/drivers/mmc/core/block.c
> > +++ b/drivers/mmc/core/block.c
> > @@ -1596,6 +1596,9 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
> > struct request *req = mmc_queue_req_to_req(mqrq);
> > struct mmc_blk_data *md = mq->blkdata;
> > bool do_rel_wr, do_data_tag;
> > + bool do_multi;
> > +
> > + do_multi = (card->uhs2_state & MMC_UHS2_INITIALIZED) ? true : false;
> >
> > mmc_blk_data_prep(mq, mqrq, recovery_mode, &do_rel_wr, &do_data_tag);
> >
> > @@ -1606,7 +1609,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
> > brq->cmd.arg <<= 9;
> > brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
> >
> > - if (brq->data.blocks > 1 || do_rel_wr) {
> > + if (brq->data.blocks > 1 || do_rel_wr || do_multi) {
>
> This looks wrong to me. UHS2 can use single block read/writes too. Right?
>
> > /* SPI multiblock writes terminate using a special
> > * token, not a STOP_TRANSMISSION request.
> > */
> > @@ -1619,6 +1622,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
> > brq->mrq.stop = NULL;
> > readcmd = MMC_READ_SINGLE_BLOCK;
> > writecmd = MMC_WRITE_BLOCK;
> > + brq->cmd.uhs2_tmode0_flag = 1;
>
> As "do_multi" is always set for UHS2, setting this flag here seems to
> be wrong/redundant.
>
> Anyway, if I understand correctly, the flag is intended to be used to
> inform the host driver whether the so-called 2L_HD_mode (half-duplex
> or full-duplex) should be used for the I/O request or not.
>
> To fix the above behaviour, I suggest we try to move the entire
> control of the flag into mmc_uhs2_prepare_cmd(). We want the flag to
> be set for multi block read/writes (CMD18 and CMD25), but only if the
> host and card supports the 2L_HD_mode too. According to my earlier
> suggestions, we should be able to check that via the bits we set
> earlier in the ios->timing.
>
> Moreover, by making mmc_uhs2_prepare_cmd() responsible for setting the
> flag, I think we can also move the definition of the flag into the
> struct uhs2_command. While at it, I suggest we also rename the flag
> into "tmode_half_duplex", to better describe its purpose, which also
> means the interpretation of the flag becomes inverted.
>
> > }
> > brq->cmd.opcode = rq_data_dir(req) == READ ? readcmd : writecmd;
> >
> > diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
> > index ad1a40446813..01f8216885ad 100644
> > --- a/drivers/mmc/core/core.c
> > +++ b/drivers/mmc/core/core.c
> > @@ -334,6 +334,8 @@ static int mmc_mrq_prep(struct mmc_host *host, struct mmc_request *mrq)
> >
> > int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
> > {
> > + struct uhs2_command uhs2_cmd;
> > + __be32 payload[4]; /* for maximum size */
> > int err;
> >
> > init_completion(&mrq->cmd_completion);
> > @@ -351,6 +353,14 @@ int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
> > if (err)
> > return err;
> >
> > + if (host->card) {
> > + if (host->card->uhs2_state & MMC_UHS2_INITIALIZED) {
> > + uhs2_cmd.payload = payload;
> > + mrq->cmd->uhs2_cmd = &uhs2_cmd;
> > + mmc_uhs2_prepare_cmd(host, mrq);
> > + }
> > + }
>
> To avoid open coding, please move this into mmc_mrq_prep() or add a
> new helper function for it.
>
I will add a helper function in the sd_ops.h and update in V7 version.
> > +
> > led_trigger_event(host->led, LED_FULL);
> > __mmc_start_request(host, mrq);
> >
> > @@ -430,6 +440,8 @@ EXPORT_SYMBOL(mmc_wait_for_req_done);
> > */
> > int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq)
> > {
> > + struct uhs2_command uhs2_cmd;
> > + __be32 payload[4]; /* for maximum size */
> > int err;
> >
> > /*
> > @@ -450,6 +462,14 @@ int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq)
> > if (err)
> > goto out_err;
> >
> > + if (host->card) {
> > + if (host->card->uhs2_state & MMC_UHS2_INITIALIZED) {
> > + uhs2_cmd.payload = payload;
> > + mrq->cmd->uhs2_cmd = &uhs2_cmd;
> > + mmc_uhs2_prepare_cmd(host, mrq);
> > + }
> > + }
>
> Ditto.
>
I will add a helper function in the sd_ops.h and update in V7 version.
> > +
> > err = host->cqe_ops->cqe_request(host, mrq);
> > if (err)
> > goto out_err;
> > diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
> > index 81c55bfd6e0c..daa1f4ccd99a 100644
> > --- a/drivers/mmc/core/mmc_ops.c
> > +++ b/drivers/mmc/core/mmc_ops.c
> > @@ -144,10 +144,24 @@ int mmc_set_dsr(struct mmc_host *host)
> > return mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
> > }
> >
> > +int __mmc_go_idle(struct mmc_host *host)
> > +{
> > + struct mmc_command cmd = {};
> > + int err;
> > +
> > + cmd.opcode = MMC_GO_IDLE_STATE;
> > + cmd.arg = 0;
> > + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
> > +
> > + err = mmc_wait_for_cmd(host, &cmd, 0);
> > + mmc_delay(1);
> > +
> > + return err;
> > +}
> > +
> > int mmc_go_idle(struct mmc_host *host)
> > {
> > int err;
> > - struct mmc_command cmd = {};
> >
> > /*
> > * Non-SPI hosts need to prevent chipselect going active during
> > @@ -163,13 +177,7 @@ int mmc_go_idle(struct mmc_host *host)
> > mmc_delay(1);
> > }
> >
> > - cmd.opcode = MMC_GO_IDLE_STATE;
> > - cmd.arg = 0;
> > - cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
> > -
> > - err = mmc_wait_for_cmd(host, &cmd, 0);
> > -
> > - mmc_delay(1);
> > + err = __mmc_go_idle(host);
>
> The above changes for mmc_go_idle() are pure refactorings, please move
> these changes into a separate patch that precedes $subject patch.
>
> This will help to simplify the review.
>
> >
> > if (!mmc_host_is_spi(host)) {
> > mmc_set_chip_select(host, MMC_CS_DONTCARE);
> > @@ -300,6 +308,7 @@ int mmc_send_adtc_data(struct mmc_card *card, struct mmc_host *host, u32 opcode,
> > * not R1 plus a data block.
> > */
> > cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
> > + cmd.uhs2_tmode0_flag = 1;
>
> As I stated above, I think we can drop this and rather manage the flag
> from mmc_uhs2_prepare_cmd() instead. From now on, I will stop to
> comment more on the use of the uhs2_tmode0_flag, as the similar
> comments applies to more places.
>
> [...]
>
> > diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h
> > index 3ba7b3cf4652..29c802dec988 100644
> > --- a/drivers/mmc/core/sd_ops.h
> > +++ b/drivers/mmc/core/sd_ops.h
> > @@ -11,6 +11,7 @@
> > #include <linux/types.h>
> >
> > struct mmc_card;
> > +struct mmc_command;
>
> Looks like this should be "struct mmc_request;" instead.
>
> > struct mmc_host;
> >
> > int mmc_app_set_bus_width(struct mmc_card *card, int width);
> > @@ -19,10 +20,12 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr);
> > int mmc_send_if_cond_pcie(struct mmc_host *host, u32 ocr);
> > int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
> > int mmc_app_send_scr(struct mmc_card *card);
> > +int mmc_decode_scr(struct mmc_card *card);
> > int mmc_sd_switch(struct mmc_card *card, int mode, int group,
> > u8 value, u8 *resp);
> > int mmc_app_sd_status(struct mmc_card *card, void *ssr);
> > int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card);
> > +void mmc_uhs2_prepare_cmd(struct mmc_host *host, struct mmc_request *mrq);
> >
> > #endif
> >
> > diff --git a/drivers/mmc/core/sd_uhs2.c b/drivers/mmc/core/sd_uhs2.c
> > index 800957f74632..a79eb08ec540 100644
> > --- a/drivers/mmc/core/sd_uhs2.c
> > +++ b/drivers/mmc/core/sd_uhs2.c
> > @@ -1,48 +1,125 @@
> > // SPDX-License-Identifier: GPL-2.0-only
> > /*
> > * Copyright (C) 2021 Linaro Ltd
> > - *
> > * Author: Ulf Hansson <[email protected]>
> > *
> > + * Copyright (C) 2014 Intel Corp, All Rights Reserved.
> > + * Author: Yi Sun <[email protected]>
> > + *
> > + * Copyright (C) 2020 Genesys Logic, Inc.
> > + * Authors: Ben Chuang <[email protected]>
> > + *
> > + * Copyright (C) 2020 Linaro Limited
> > + * Author: AKASHI Takahiro <[email protected]>
> > + *
> > + * Copyright (C) 2022 Genesys Logic, Inc.
> > + * Authors: Jason Lai <[email protected]>
> > + *
> > * Support for SD UHS-II cards
> > */
> > #include <linux/err.h>
> > +#include <linux/pm_runtime.h>
> >
> > #include <linux/mmc/host.h>
> > #include <linux/mmc/card.h>
> > +#include <linux/mmc/mmc.h>
> > +#include <linux/mmc/sd.h>
> > +#include <linux/mmc/sd_uhs2.h>
> >
> > +#include "card.h"
> > #include "core.h"
> > #include "bus.h"
> > #include "sd.h"
> > +#include "sd_ops.h"
> > #include "mmc_ops.h"
> >
> > +#define UHS2_WAIT_CFG_COMPLETE_PERIOD_US (1 * 1000) /* 1ms */
> > +#define UHS2_WAIT_CFG_COMPLETE_TIMEOUT_MS 100 /* 100ms */
> > +
> > static const unsigned int sd_uhs2_freqs[] = { 52000000, 26000000 };
> > +int sd_uhs2_reinit(struct mmc_host *host);
>
> This should be static - and please don't use forward declaration,
> unless it's really necessary.
>
> >
> > -static int sd_uhs2_set_ios(struct mmc_host *host)
> > +/*
> > + * Internal function that does the actual ios call to the host driver,
> > + * optionally printing some debug output.
> > + */
> > +static inline int sd_uhs2_set_ios(struct mmc_host *host)
> > {
> > struct mmc_ios *ios = &host->ios;
> >
> > + pr_debug("%s: clock %uHz powermode %u Vdd %u timing %u\n",
> > + mmc_hostname(host), ios->clock, ios->power_mode, ios->vdd, ios->timing);
> > +
> > return host->ops->uhs2_set_ios(host, ios);
>
> I have made some more thinking around the uhs2 host callbacks.
>
> As we are using the ->uhs2_control() callback for all the other uhs2
> operations, it seems silly to have a separate callback for the ios. In
> other words, I suggest that we drop the ->uhs2_set_ios() callback
> altogether and replace the two users of the above function with direct
> calls to the ->uhs2_control() callback. See more below.
>
I will update it in V7 version.
> > }
> >
> > static int sd_uhs2_power_up(struct mmc_host *host)
> > {
> > + int err;
> > +
> > + if (host->ios.power_mode == MMC_POWER_ON)
> > + return 0;
> > +
> > host->ios.vdd = fls(host->ocr_avail) - 1;
> > host->ios.clock = host->f_init;
> > host->ios.timing = MMC_TIMING_SD_UHS2;
> > - host->ios.power_mode = MMC_POWER_UP;
> > + host->ios.power_mode = MMC_POWER_ON;
> >
> > - return sd_uhs2_set_ios(host);
> > + err = sd_uhs2_set_ios(host);
>
> As stated above, I suggest we replace the above call with:
> err = host->ops->uhs2_control(host, UHS2_SET_IOS);
>
I will update it in V7 version.
> > +
> > + mmc_delay(host->uhs2_ios.power_delay_ms);
> > +
> > + return err;
> > }
> >
> > -static void sd_uhs2_power_off(struct mmc_host *host)
> > +static int sd_uhs2_power_off(struct mmc_host *host)
> > {
> > + if (host->ios.power_mode == MMC_POWER_OFF)
> > + return 0;
> > +
> > host->ios.vdd = 0;
> > host->ios.clock = 0;
> > host->ios.timing = MMC_TIMING_LEGACY;
> > host->ios.power_mode = MMC_POWER_OFF;
> >
> > - sd_uhs2_set_ios(host);
> > + return sd_uhs2_set_ios(host);
>
> Similar to the above, I suggest we replace the above call with:
> return host->ops->uhs2_control(host, UHS2_SET_IOS);
>
I will update it in V7 version.
> >
> > /*
> > @@ -61,6 +146,88 @@ static int sd_uhs2_phy_init(struct mmc_host *host)
> > */
> > static int sd_uhs2_dev_init(struct mmc_host *host)
> > {
> > + struct mmc_command cmd = {0};
> > + struct uhs2_command uhs2_cmd = {};
> > + u32 cnt;
> > + u32 dap, gap, resp_gap;
> > + u16 header, arg;
> > + __be32 payload[UHS2_DEV_INIT_PAYLOAD_LEN];
> > + u8 gd = 0;
> > + u8 resp[UHS2_DEV_ENUM_RESP_LEN] = {0};
> > + int err;
> > +
> > + dap = host->uhs2_caps.dap;
> > + gap = host->uhs2_caps.gap;
> > +
> > + /*
> > + * Refer to UHS-II Addendum Version 1.02 Figure 6-21 to see DEVICE_INIT CCMD format.
> > + * Head:
> > + * - Control Write(R/W=1) with 4-Byte payload(PLEN=01b).
> > + * - IOADR = CMD_BASE + 002h
> > + * Payload:
> > + * - bit [3:0] : GAP(Group Allocated Power)
> > + * - bit [7:4] : GD(Group Descriptor)
> > + * - bit [11] : Complete Flag
> > + * - bit [15:12]: DAP(Device Allocated Power)
> > + */
> > + header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD;
> > + arg = ((UHS2_DEV_CMD_DEVICE_INIT & 0xFF) << 8) |
> > + UHS2_NATIVE_CMD_WRITE |
> > + UHS2_NATIVE_CMD_PLEN_4B |
> > + (UHS2_DEV_CMD_DEVICE_INIT >> 8);
> > +
> > + /*
> > + * Refer to UHS-II Addendum Version 1.02 section 6.3.1.
> > + * Max. time from DEVICE_INIT CCMD EOP reception on Device
> > + * Rx to its SOP transmission on Device Tx(Tfwd_init_cmd) is
> > + * 1 second.
> > + */
> > + cmd.busy_timeout = 1000;
> > +
> > + /*
> > + * Refer to UHS-II Addendum Version 1.02 section 6.2.6.3.
> > + * When the number of the DEVICE_INIT commands is reach to
> > + * 30 tiems, Host shall stop issuing DEVICE_INIT command
> > + * and regard it as an error.
> > + */
> > + for (cnt = 0; cnt < 30; cnt++) {
> > + payload[0] = ((dap & 0xF) << 12) |
> > + UHS2_DEV_INIT_COMPLETE_FLAG |
> > + ((gd & 0xF) << 4) |
> > + (gap & 0xF);
> > +
> > + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg,
> > + payload, UHS2_DEV_INIT_PAYLOAD_LEN,
> > + resp, UHS2_DEV_INIT_RESP_LEN);
> > +
> > + err = mmc_wait_for_cmd(host, &cmd, 0);
> > +
>
> Nitpick: Unnecessary line break.
>
I will update it in V7 version.
> > + if (err) {
> > + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> > + mmc_hostname(host), __func__, err);
> > + return err;
> > + }
> > +
> > + if (resp[3] != (UHS2_DEV_CMD_DEVICE_INIT & 0xFF)) {
> > + pr_err("%s: DEVICE_INIT response is wrong!\n",
> > + mmc_hostname(host));
> > + return -EIO;
> > + }
> > +
> > + if (resp[5] & 0x8) {
> > + host->uhs2_caps.group_desc = gd;
> > + return 0;
> > + }
> > + resp_gap = resp[4] & 0x0F;
> > + if (gap == resp_gap)
> > + gd++;
> > + }
> > + if (cnt == 30) {
>
> There is no need to check cnt here. It's always 30 if we reach this point.
>
I will drop it and update in V7 version.
> > + pr_err("%s: DEVICE_INIT fail, already 30 times!\n",
> > + mmc_hostname(host));
> > + return -EIO;
> > + }
> > +
> > return 0;
> > }
>
> [...]
>
> > static int sd_uhs2_config_write(struct mmc_host *host, struct mmc_card *card)
> > {
> > + struct mmc_command cmd = {0};
> > + struct uhs2_command uhs2_cmd = {};
> > + u16 header, arg;
> > + __be32 payload[UHS2_CFG_WRITE_PAYLOAD_LEN];
> > + u8 nMinDataGap;
> > + int err;
> > + u8 resp[5] = {0};
> > +
> > + /*
> > + * Use Control Write CCMD to set Generic Setting in Configuration Register.
> > + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> > + * - IOADR = Generic Setting Register(CFG_BASE + 008h)
> > + * - Payload = New contents to be written to Generic Setting Register
> > + */
> > + header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | card->uhs2_config.node_id;
> > + arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
> > + UHS2_NATIVE_CMD_WRITE |
> > + UHS2_NATIVE_CMD_PLEN_8B |
> > + (UHS2_DEV_CONFIG_GEN_SET >> 8);
> > +
> > + if (card->uhs2_config.n_lanes == UHS2_DEV_CONFIG_2L_HD_FD &&
> > + host->uhs2_caps.n_lanes == UHS2_DEV_CONFIG_2L_HD_FD) {
> > + /* Support HD */
> > + host->uhs2_ios.is_2L_HD_mode = true;
> > + nMinDataGap = 1;
> > + } else {
> > + /* Only support 2L-FD so far */
> > + host->uhs2_ios.is_2L_HD_mode = false;
> > + nMinDataGap = 3;
> > + }
> > +
> > + /*
> > + * Most UHS-II cards only support FD and 2L-HD mode. Other lane numbers
> > + * defined in UHS-II addendem Ver1.01 are optional.
> > + */
> > + host->uhs2_caps.n_lanes_set = UHS2_DEV_CONFIG_GEN_SET_2L_FD_HD;
> > + card->uhs2_config.n_lanes_set = UHS2_DEV_CONFIG_GEN_SET_2L_FD_HD;
> > +
> > + payload[0] = card->uhs2_config.n_lanes_set << UHS2_DEV_CONFIG_N_LANES_POS;
> > + payload[1] = 0;
> > + payload[0] = cpu_to_be32(payload[0]);
> > + payload[1] = cpu_to_be32(payload[1]);
> > +
> > + /*
> > + * There is no payload because per spec, there should be
> > + * no payload field for read CCMD.
> > + * Plen is set in arg. Per spec, plen for read CCMD
> > + * represents the len of read data which is assigned in payload
> > + * of following RES (p136).
> > + */
> > + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> > + NULL, 0);
> > +
> > + err = mmc_wait_for_cmd(host, &cmd, 0);
> > + if (err) {
> > + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> > + mmc_hostname(host), __func__, err);
> > + return err;
> > + }
> > +
> > + /*
> > + * Use Control Write CCMD to set PHY Setting in Configuration Register.
> > + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> > + * - IOADR = PHY Setting Register(CFG_BASE + 00Ah)
> > + * - Payload = New contents to be written to PHY Setting Register
> > + */
> > + arg = ((UHS2_DEV_CONFIG_PHY_SET & 0xFF) << 8) |
> > + UHS2_NATIVE_CMD_WRITE |
> > + UHS2_NATIVE_CMD_PLEN_8B |
> > + (UHS2_DEV_CONFIG_PHY_SET >> 8);
> > +
> > + if (host->uhs2_caps.speed_range == UHS2_DEV_CONFIG_PHY_SET_SPEED_B) {
> > + card->uhs2_state |= MMC_UHS2_SPEED_B;
>
> As I suggested earlier, let's set a corresponding bit in ios->timing instead.
>
> > + card->uhs2_config.speed_range_set =
> > + UHS2_DEV_CONFIG_PHY_SET_SPEED_B;
> > + } else {
> > + card->uhs2_config.speed_range_set = UHS2_DEV_CONFIG_PHY_SET_SPEED_A;
> > + card->uhs2_state &= ~MMC_UHS2_SPEED_B;
>
> As I suggested earlier, I think it should be sufficient to clear this
> in sd_uhs2_power_off() (by resetting ios->timing).
>
> > + }
> > +
> > + payload[0] = card->uhs2_config.speed_range_set << UHS2_DEV_CONFIG_PHY_SET_SPEED_POS;
> > +
> > + card->uhs2_config.n_lss_sync_set = (max(card->uhs2_config.n_lss_sync,
> > + host->uhs2_caps.n_lss_sync) >> 2) &
> > + UHS2_DEV_CONFIG_N_LSS_SYN_MASK;
> > + host->uhs2_caps.n_lss_sync_set = card->uhs2_config.n_lss_sync_set;
> > +
> > + card->uhs2_config.n_lss_dir_set = (max(card->uhs2_config.n_lss_dir,
> > + host->uhs2_caps.n_lss_dir) >> 3) &
> > + UHS2_DEV_CONFIG_N_LSS_DIR_MASK;
> > + host->uhs2_caps.n_lss_dir_set = card->uhs2_config.n_lss_dir_set;
> > +
> > + payload[1] = (card->uhs2_config.n_lss_dir_set << UHS2_DEV_CONFIG_N_LSS_DIR_POS) |
> > + card->uhs2_config.n_lss_sync_set;
> > + payload[0] = cpu_to_be32(payload[0]);
> > + payload[1] = cpu_to_be32(payload[1]);
> > +
> > + memset(resp, 0, sizeof(resp));
> > +
> > + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> > + resp, UHS2_CFG_WRITE_PHY_SET_RESP_LEN);
> > +
> > + err = mmc_wait_for_cmd(host, &cmd, 0);
> > + if (err) {
> > + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> > + mmc_hostname(host), __func__, err);
> > + return err;
> > + }
> > +
> > + if ((resp[2] & 0x80)) {
> > + pr_err("%s: %s: UHS2 CMD not accepted, resp= 0x%x!\n",
> > + mmc_hostname(host), __func__, resp[2]);
> > + return -EIO;
> > + }
> > +
> > + /*
> > + * Use Control Write CCMD to set LINK/TRAN Setting in Configuration Register.
> > + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> > + * - IOADR = LINK/TRAN Setting Register(CFG_BASE + 00Ch)
> > + * - Payload = New contents to be written to LINK/TRAN Setting Register
> > + */
> > + arg = ((UHS2_DEV_CONFIG_LINK_TRAN_SET & 0xFF) << 8) |
> > + UHS2_NATIVE_CMD_WRITE |
> > + UHS2_NATIVE_CMD_PLEN_8B |
> > + (UHS2_DEV_CONFIG_LINK_TRAN_SET >> 8);
> > +
> > + if (card->uhs2_config.app_type == UHS2_DEV_CONFIG_APP_SD_MEM)
> > + card->uhs2_config.maxblk_len_set = UHS2_DEV_CONFIG_LT_SET_MAX_BLK_LEN;
> > + else
> > + card->uhs2_config.maxblk_len_set = min(card->uhs2_config.maxblk_len,
> > + host->uhs2_caps.maxblk_len);
> > + host->uhs2_caps.maxblk_len_set = card->uhs2_config.maxblk_len_set;
> > +
> > + card->uhs2_config.n_fcu_set = min(card->uhs2_config.n_fcu, host->uhs2_caps.n_fcu);
> > + host->uhs2_caps.n_fcu_set = card->uhs2_config.n_fcu_set;
> > +
> > + card->uhs2_config.n_data_gap_set = max(nMinDataGap, card->uhs2_config.n_data_gap);
> > + host->uhs2_caps.n_data_gap_set = card->uhs2_config.n_data_gap_set;
> > +
> > + host->uhs2_caps.max_retry_set = 3;
> > + card->uhs2_config.max_retry_set = host->uhs2_caps.max_retry_set;
> > +
> > + payload[0] = (card->uhs2_config.maxblk_len_set << UHS2_DEV_CONFIG_MAX_BLK_LEN_POS) |
> > + (card->uhs2_config.max_retry_set << UHS2_DEV_CONFIG_LT_SET_MAX_RETRY_POS) |
> > + (card->uhs2_config.n_fcu_set << UHS2_DEV_CONFIG_N_FCU_POS);
> > + payload[1] = card->uhs2_config.n_data_gap_set;
> > + payload[0] = cpu_to_be32(payload[0]);
> > + payload[1] = cpu_to_be32(payload[1]);
> > +
> > + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> > + NULL, 0);
> > +
> > + err = mmc_wait_for_cmd(host, &cmd, 0);
> > + if (err) {
> > + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> > + mmc_hostname(host), __func__, err);
> > + return err;
> > + }
> > +
> > + /*
> > + * Use Control Write CCMD to set Config Completion(payload bit 63) in Generic Setting
> > + * Register.
> > + * Header:
> > + * - Control Write(R/W=1) with 8-Byte payload(PLEN=10b).
> > + * - IOADR = PGeneric Setting Register(CFG_BASE + 008h)
> > + * Payload:
> > + * - bit [63]: Config Completion
> > + *
> > + * DLSM transits to Active state immediately when Config Completion is set to 1.
> > + */
> > + arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
> > + UHS2_NATIVE_CMD_WRITE |
> > + UHS2_NATIVE_CMD_PLEN_8B |
> > + (UHS2_DEV_CONFIG_GEN_SET >> 8);
> > +
> > + payload[0] = 0;
> > + payload[1] = UHS2_DEV_CONFIG_GEN_SET_CFG_COMPLETE;
> > + payload[0] = cpu_to_be32(payload[0]);
> > + payload[1] = cpu_to_be32(payload[1]);
> > +
> > + memset(resp, 0, sizeof(resp));
> > + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, payload, UHS2_CFG_WRITE_PAYLOAD_LEN,
> > + resp, UHS2_CFG_WRITE_GENERIC_SET_RESP_LEN);
> > +
> > + err = mmc_wait_for_cmd(host, &cmd, 0);
> > + if (err) {
> > + pr_err("%s: %s: UHS2 CMD send fail, err= 0x%x!\n",
> > + mmc_hostname(host), __func__, err);
> > + return err;
> > + }
> > +
> > + /* Set host Config Setting registers */
> > + err = host->ops->uhs2_control(host, UHS2_SET_CONFIG);
> > + if (err) {
> > + pr_err("%s: %s: UHS2 SET_CONFIG fail!\n", mmc_hostname(host), __func__);
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
>
> [...]
>
> > +static int sd_uhs2_change_speed(struct mmc_host *host, u32 node_id)
> > +{
> > + struct mmc_command cmd = {0};
> > + struct uhs2_command uhs2_cmd = {};
> > + u16 header, arg;
> > + int err;
> > + struct sd_uhs2_wait_active_state_data cb_data = {
> > + .host = host,
> > + .cmd = &cmd
> > + };
> > +
> > + /* Change Speed Range at controller side. */
> > + err = host->ops->uhs2_control(host, UHS2_SET_SPEED_B);
>
> As I asked for in patch5, is this call really needed for sdhci?
>
> > + if (err) {
> > + pr_err("%s: %s: UHS2 SET_SPEED fail!\n", mmc_hostname(host), __func__);
> > + return err;
> > + }
> > +
> > + err = sd_uhs2_go_dormant(host, node_id);
> > + if (err) {
> > + pr_err("%s: %s: UHS2 GO_DORMANT_STATE fail, err= 0x%x!\n",
> > + mmc_hostname(host), __func__, err);
> > + return err;
> > + }
> > +
> > + /*
> > + * Use Control Read CCMD to check Config Completion(bit 63) in Generic Setting Register.
> > + * - Control Read(R/W=0) with 8-Byte payload(PLEN=10b).
> > + * - IOADR = Generic Setting Register(CFG_BASE + 008h)
> > + *
> > + * When UHS-II card been switched to new speed mode, it will set Config Completion to 1.
> > + */
> > + header = UHS2_NATIVE_PACKET | UHS2_PACKET_TYPE_CCMD | node_id;
> > + arg = ((UHS2_DEV_CONFIG_GEN_SET & 0xFF) << 8) |
> > + UHS2_NATIVE_CMD_READ |
> > + UHS2_NATIVE_CMD_PLEN_8B |
> > + (UHS2_DEV_CONFIG_GEN_SET >> 8);
> > +
> > + sd_uhs2_cmd_assemble(&cmd, &uhs2_cmd, header, arg, NULL, 0, NULL, 0);
> > + err = __mmc_poll_for_busy(host, UHS2_WAIT_CFG_COMPLETE_PERIOD_US,
> > + UHS2_WAIT_CFG_COMPLETE_TIMEOUT_MS,
> > + &__sd_uhs2_wait_active_state_cb, &cb_data);
> > + if (err) {
> > + pr_err("%s: %s: Not switch to Active in 100 ms\n", mmc_hostname(host), __func__);
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int sd_uhs2_get_ro(struct mmc_host *host)
> > +{
> > + /*
> > + * Some systems don't feature a write-protect pin and don't need one.
> > + * E.g. because they only have micro-SD card slot. For those systems
> > + * assume that the SD card is always read-write.
> > + */
> > + if (host->caps2 & MMC_CAP2_NO_WRITE_PROTECT)
> > + return 0;
> > +
> > + if (!host->ops->get_ro)
> > + return -1;
> > +
> > + return host->ops->get_ro(host);
>
> Let's avoid the open coding and use mmc_sd_get_ro() instead.
>
> > +}
> > +
> > +/*
> > + * Mask off any voltages we don't support and select
> > + * the lowest voltage
> > + */
> > +u32 sd_uhs2_select_voltage(struct mmc_host *host, u32 ocr)
> > +{
> > + int bit;
> > + int err;
> > +
> > + /*
> > + * Sanity check the voltages that the card claims to
> > + * support.
> > + */
> > + if (ocr & 0x7F) {
> > + dev_warn(mmc_dev(host), "card claims to support voltages below defined range\n");
> > + ocr &= ~0x7F;
> > + }
> > +
> > + ocr &= host->ocr_avail;
> > + if (!ocr) {
> > + dev_warn(mmc_dev(host), "no support for card's volts\n");
> > + return 0;
> > + }
> > +
> > + if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) {
> > + bit = ffs(ocr) - 1;
> > + ocr &= 3 << bit;
> > + /* Power cycle */
> > + err = sd_uhs2_power_off(host);
> > + if (err)
> > + return 0;
> > + err = sd_uhs2_reinit(host);
>
> As also pointed out by Adrian, this path becomes circular and doesn't
> work as expected.
>
> At this point, I would prefer to keep things as simple as possible, so
> I suggest that we drop this path entirely for UHS2.
>
Can we use sd_uhs2_power and sd_uhs2_power_up instead of the
sd_uhs2_reinit after I reference the mmc_power_cycle function?
> > + if (err)
> > + return 0;
> > + } else {
> > + bit = fls(ocr) - 1;
> > + ocr &= 3 << bit;
>
> Note that, in mmc_select_voltage() the corresponding code has been
> updated in commit 39a72dbfe188 ("mmc: core: properly select voltage
> range without power cycle") that fixes a real problem for us.
>
> By taking the above observations into consideration, it looks like it
> may be better to reuse mmc_select_voltage() for UHS-II after all. To
> prevent the power cycle path for UHS-II, we can just add a check for
> the ios->timings in there. That should work, I think.
>
> > + if (bit != host->ios.vdd)
> > + dev_warn(mmc_dev(host), "exceeding card's volts\n");
> > + }
> > +
> > + return ocr;
> > +}
> > +
> > /*
> > * Initialize the UHS-II card through the SD-TRAN transport layer. This enables
> > * commands/requests to be backwards compatible through the legacy SD protocol.
> > @@ -107,14 +893,149 @@ static int sd_uhs2_config_write(struct mmc_host *host, struct mmc_card *card)
> > */
> > static int sd_uhs2_legacy_init(struct mmc_host *host, struct mmc_card *card)
>
> I decided to postpone the review of the following parts below, which
> consists of the legacy card initialization (SD-tran) and the
> power-management support - as it looks like there are enough for you
> to work on for a while.
>
> Let me also ask about the power management support - did you test the
> system suspend/resume path? Just to understand, if this is more an
> attempt to make it work or whether it's actually tested/works?
>
> [...]
>
> Kind regards
> Uffe
Thanks, Victor Shih
[...]
> > > +}
> > > +
> > > +/*
> > > + * Mask off any voltages we don't support and select
> > > + * the lowest voltage
> > > + */
> > > +u32 sd_uhs2_select_voltage(struct mmc_host *host, u32 ocr)
> > > +{
> > > + int bit;
> > > + int err;
> > > +
> > > + /*
> > > + * Sanity check the voltages that the card claims to
> > > + * support.
> > > + */
> > > + if (ocr & 0x7F) {
> > > + dev_warn(mmc_dev(host), "card claims to support voltages below defined range\n");
> > > + ocr &= ~0x7F;
> > > + }
> > > +
> > > + ocr &= host->ocr_avail;
> > > + if (!ocr) {
> > > + dev_warn(mmc_dev(host), "no support for card's volts\n");
> > > + return 0;
> > > + }
> > > +
> > > + if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) {
> > > + bit = ffs(ocr) - 1;
> > > + ocr &= 3 << bit;
> > > + /* Power cycle */
> > > + err = sd_uhs2_power_off(host);
> > > + if (err)
> > > + return 0;
> > > + err = sd_uhs2_reinit(host);
> >
> > As also pointed out by Adrian, this path becomes circular and doesn't
> > work as expected.
> >
> > At this point, I would prefer to keep things as simple as possible, so
> > I suggest that we drop this path entirely for UHS2.
> >
>
> Can we use sd_uhs2_power and sd_uhs2_power_up instead of the
> sd_uhs2_reinit after I reference the mmc_power_cycle function?
Sorry, but I don't quite understand how that would help to make this work!?
Anyway, please have a look at the corresponding comment below too. The
point is, I think it's better if we can update (or refactor) and make
use of mmc_select_voltage() instead.
>
> > > + if (err)
> > > + return 0;
> > > + } else {
> > > + bit = fls(ocr) - 1;
> > > + ocr &= 3 << bit;
> >
> > Note that, in mmc_select_voltage() the corresponding code has been
> > updated in commit 39a72dbfe188 ("mmc: core: properly select voltage
> > range without power cycle") that fixes a real problem for us.
> >
> > By taking the above observations into consideration, it looks like it
> > may be better to reuse mmc_select_voltage() for UHS-II after all. To
> > prevent the power cycle path for UHS-II, we can just add a check for
> > the ios->timings in there. That should work, I think.
> >
> > > + if (bit != host->ios.vdd)
> > > + dev_warn(mmc_dev(host), "exceeding card's volts\n");
> > > + }
> > > +
> > > + return ocr;
> > > +}
> > > +
[...]
Kind regards
Uffe