[RFC PATCH V3 12/21] mmc: sdhci: UHS-II support, add hooks for additional operations

From: Ben Chuang
Date: Fri Jul 10 2020 - 07:10:14 EST


From: Ben Chuang <ben.chuang@xxxxxxxxxxxxxxxxxxx>

In this commit, UHS-II related operations will be called via a function
pointer array, sdhci_uhs2_ops, in order to make UHS-II support as
a kernel module.
This array will be initialized only if CONFIG_MMC_SDHCI_UHS2 is enabled
and when the UHS-II module is loaded. Otherwise, all the functions
stay void.

Signed-off-by: Ben Chuang <ben.chuang@xxxxxxxxxxxxxxxxxxx>
Signed-off-by: AKASHI Takahiro <takahiro.akashi@xxxxxxxxxx>
---
drivers/mmc/host/sdhci.c | 152 ++++++++++++++++++++++++++++++++++-----
1 file changed, 136 insertions(+), 16 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index aaf41954511a..5511649946b9 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -32,8 +32,12 @@
#include <linux/mmc/card.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/slot-gpio.h>
+#include <linux/mmc/uhs2.h>
+#include <linux/pci.h>

#include "sdhci.h"
+#include "sdhci-uhs2.h"
+#include "sdhci-pci.h"

#define DRIVER_NAME "sdhci"

@@ -45,6 +49,11 @@

#define MAX_TUNING_LOOP 40

+#if IS_ENABLED(CONFIG_MMC_SDHCI_UHS2)
+struct sdhci_uhs2_ops sdhci_uhs2_ops;
+EXPORT_SYMBOL_GPL(sdhci_uhs2_ops);
+#endif
+
static unsigned int debug_quirks = 0;
static unsigned int debug_quirks2;

@@ -1041,8 +1050,11 @@ EXPORT_SYMBOL_GPL(sdhci_set_data_timeout_irq);

void __sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
{
+ u8 count;
+
bool too_big = false;
- u8 count = sdhci_calc_timeout(host, cmd, &too_big);
+
+ count = sdhci_calc_timeout(host, cmd, &too_big);

if (too_big &&
host->quirks2 & SDHCI_QUIRK2_DISABLE_HW_TIMEOUT) {
@@ -1053,6 +1065,11 @@ void __sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
}

sdhci_writeb(host, count, SDHCI_TIMEOUT_CONTROL);
+
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->mmc->flags & MMC_UHS2_SUPPORT &&
+ sdhci_uhs2_ops.set_timeout)
+ sdhci_uhs2_ops.set_timeout(host);
}
EXPORT_SYMBOL_GPL(__sdhci_set_timeout);

@@ -1191,7 +1208,14 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)

sdhci_set_transfer_irqs(host);

- sdhci_set_block_info(host, data);
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->mmc->flags & MMC_UHS2_SUPPORT &&
+ host->mmc->flags & MMC_UHS2_INITIALIZED) {
+ sdhci_writew(host, data->blksz, SDHCI_UHS2_BLOCK_SIZE);
+ sdhci_writew(host, data->blocks, SDHCI_UHS2_BLOCK_COUNT);
+ } else {
+ sdhci_set_block_info(host, data);
+ }
}

#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA)
@@ -1439,6 +1463,13 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
u16 mode = 0;
struct mmc_data *data = cmd->data;

+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->mmc->flags & MMC_UHS2_SUPPORT) {
+ if (sdhci_uhs2_ops.set_transfer_mode)
+ sdhci_uhs2_ops.set_transfer_mode(host, cmd);
+ return;
+ }
+
if (data == NULL) {
if (host->quirks2 &
SDHCI_QUIRK2_CLEAR_TRANSFERMODE_REG_BEFORE_CMD) {
@@ -1570,6 +1601,12 @@ static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
else
data->bytes_xfered = data->blksz * data->blocks;

+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->mmc->flags & MMC_UHS2_INITIALIZED) {
+ __sdhci_finish_mrq(host, data->mrq);
+ return;
+ }
+
/*
* Need to send CMD12 if -
* a) open-ended multiblock transfer not using auto CMD12 (no CMD23)
@@ -1654,7 +1691,8 @@ 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);
+ if (!IS_ENABLED(CONFIG_MMC_SDHCI_UHS2))
+ sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);

sdhci_set_transfer_mode(host, cmd);

@@ -1699,6 +1737,17 @@ 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);

+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ (host->mmc->flags & MMC_UHS2_SUPPORT)) {
+ if (sdhci_uhs2_ops.send_command)
+ sdhci_uhs2_ops.send_command(host, cmd);
+
+ return true;
+ }
+
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2))
+ sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
+
sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);

return true;
@@ -1780,13 +1829,20 @@ static void sdhci_finish_command(struct sdhci_host *host)
{
struct mmc_command *cmd = host->cmd;

- host->cmd = NULL;
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->mmc->flags & MMC_UHS2_SUPPORT) {
+ if (sdhci_uhs2_ops.finish_command)
+ sdhci_uhs2_ops.finish_command(host);
+ } else {
+ host->cmd = NULL;

- if (cmd->flags & MMC_RSP_PRESENT) {
- if (cmd->flags & MMC_RSP_136) {
- sdhci_read_rsp_136(host, cmd);
- } else {
- cmd->resp[0] = sdhci_readl(host, SDHCI_RESPONSE);
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ sdhci_read_rsp_136(host, cmd);
+ } else {
+ cmd->resp[0] = sdhci_readl(host,
+ SDHCI_RESPONSE);
+ }
}
}

@@ -1809,6 +1865,7 @@ static void sdhci_finish_command(struct sdhci_host *host)
} else if (!(host->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) &&
cmd == host->data_cmd) {
/* Command complete before busy is ended */
+ host->cmd = NULL;
return;
}
}
@@ -1828,6 +1885,8 @@ static void sdhci_finish_command(struct sdhci_host *host)
if (!cmd->data)
__sdhci_finish_mrq(host, cmd->mrq);
}
+
+ host->cmd = NULL;
}

static u16 sdhci_get_preset_value(struct sdhci_host *host)
@@ -1855,6 +1914,11 @@ static u16 sdhci_get_preset_value(struct sdhci_host *host)
case MMC_TIMING_MMC_HS400:
preset = sdhci_readw(host, SDHCI_PRESET_FOR_HS400);
break;
+#if IS_ENABLED(CONFIG_MMC_SDHCI_UHS2)
+ case MMC_TIMING_UHS2:
+ preset = sdhci_readw(host, SDHCI_PRESET_FOR_UHS2);
+ break;
+#endif
default:
pr_warn("%s: Invalid UHS-I mode selected\n",
mmc_hostname(host->mmc));
@@ -2095,7 +2159,6 @@ void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);

pwr |= SDHCI_POWER_ON;
-
sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);

if (host->quirks2 & SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON)
@@ -2261,6 +2324,7 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct sdhci_host *host = mmc_priv(mmc);
u8 ctrl;
+ u16 ctrl_2;

if (ios->power_mode == MMC_POWER_UNDEFINED)
return;
@@ -2287,6 +2351,10 @@ 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) {
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ ios->timing == MMC_TIMING_UHS2)
+ host->timing = ios->timing;
+
host->ops->set_clock(host, ios->clock);
host->clock = ios->clock;

@@ -2308,6 +2376,18 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
else
sdhci_set_power(host, ios->power_mode, ios->vdd);

+ /* 4.0 host support */
+ if (host->version >= SDHCI_SPEC_400) {
+ /* UHS2 Support */
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->mmc->flags & MMC_UHS2_SUPPORT &&
+ host->mmc->caps & MMC_CAP_UHS2) {
+ if (sdhci_uhs2_ops.do_set_ios)
+ sdhci_uhs2_ops.do_set_ios(host, ios);
+ return;
+ }
+ }
+
if (host->ops->platform_send_init_74_clocks)
host->ops->platform_send_init_74_clocks(host, ios->power_mode);

@@ -2331,7 +2411,7 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
}

if (host->version >= SDHCI_SPEC_300) {
- u16 clk, ctrl_2;
+ u16 clk;

if (!host->preset_enabled) {
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
@@ -3173,11 +3253,19 @@ static bool sdhci_request_done(struct sdhci_host *host)
/* This is to force an update */
host->ops->set_clock(host, host->clock);

- /* Spec says we should do both at the same time, but Ricoh
- controllers do not like that. */
- sdhci_do_reset(host, SDHCI_RESET_CMD);
- sdhci_do_reset(host, SDHCI_RESET_DATA);
-
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->mmc->flags & MMC_UHS2_INITIALIZED) {
+ if (sdhci_uhs2_ops.reset)
+ sdhci_uhs2_ops.reset(host,
+ SDHCI_UHS2_SW_RESET_SD);
+ } else {
+ /*
+ * Spec says we should do both at the same time, but
+ * Ricoh controllers do not like that.
+ */
+ sdhci_do_reset(host, SDHCI_RESET_CMD);
+ sdhci_do_reset(host, SDHCI_RESET_DATA);
+ }
host->pending_reset = false;
}

@@ -3532,6 +3620,13 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
SDHCI_INT_BUS_POWER);
sdhci_writel(host, mask, SDHCI_INT_STATUS);

+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ intmask & SDHCI_INT_ERROR &&
+ host->mmc->flags & MMC_UHS2_SUPPORT) {
+ if (sdhci_uhs2_ops.irq)
+ sdhci_uhs2_ops.irq(host);
+ }
+
if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
SDHCI_CARD_PRESENT;
@@ -4717,6 +4812,14 @@ int sdhci_setup_host(struct sdhci_host *host)
/* This may alter mmc->*_blk_* parameters */
sdhci_allocate_bounce_buffer(host);

+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ host->version >= SDHCI_SPEC_400 &&
+ sdhci_uhs2_ops.add_host) {
+ ret = sdhci_uhs2_ops.add_host(host, host->caps1);
+ if (ret)
+ goto unreg;
+ }
+
return 0;

unreg:
@@ -4738,6 +4841,8 @@ void sdhci_cleanup_host(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;

+ /* FIXME: Do we have to do some cleanup for UHS2 here? */
+
if (!IS_ERR(mmc->supply.vqmmc))
regulator_disable(mmc->supply.vqmmc);

@@ -4766,6 +4871,14 @@ int __sdhci_add_host(struct sdhci_host *host)
mmc->cqe_ops = NULL;
}

+ if ((mmc->caps & MMC_CAP_UHS2) && !host->v4_mode) {
+ /* host doesn't want to enable UHS2 support */
+ mmc->caps &= ~MMC_CAP_UHS2;
+ mmc->flags &= ~MMC_UHS2_SUPPORT;
+
+ /* FIXME: Do we have to do some cleanup here? */
+ }
+
host->complete_wq = alloc_workqueue("sdhci", flags, 0);
if (!host->complete_wq)
return -ENOMEM;
@@ -4812,6 +4925,9 @@ int __sdhci_add_host(struct sdhci_host *host)
unled:
sdhci_led_unregister(host);
unirq:
+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ sdhci_uhs2_ops.remove_host)
+ sdhci_uhs2_ops.remove_host(host, 0);
sdhci_do_reset(host, SDHCI_RESET_ALL);
sdhci_writel(host, 0, SDHCI_INT_ENABLE);
sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
@@ -4869,6 +4985,10 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)

sdhci_led_unregister(host);

+ if (IS_ENABLED(CONFIG_MMC_SDHCI_UHS2) &&
+ sdhci_uhs2_ops.remove_host)
+ sdhci_uhs2_ops.remove_host(host, dead);
+
if (!dead)
sdhci_do_reset(host, SDHCI_RESET_ALL);

--
2.27.0