From b6d2d81c5c2de6b3c69da3be9b2044dd008dbe45 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 17 Feb 2017 10:56:39 +0800 Subject: [PATCH 001/148] mmc: dw_mmc: improve the timeout polling code Just use the readl_poll_timeout{_atomic} to avold open coding them. Signed-off-by: Shawn Lin Signed-off-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 92 +++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 53 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 8718432751c5..5372460d8bba 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,8 @@ struct idmac_desc_64addr { u32 des0; /* Control Descriptor */ +#define IDMAC_OWN_CLR64(x) \ + !((x) & cpu_to_le32(IDMAC_DES0_OWN)) u32 des1; /* Reserved */ @@ -343,7 +346,7 @@ static u32 dw_mci_prep_stop_abort(struct dw_mci *host, struct mmc_command *cmd) static void dw_mci_wait_while_busy(struct dw_mci *host, u32 cmd_flags) { - unsigned long timeout = jiffies + msecs_to_jiffies(500); + u32 status; /* * Databook says that before issuing a new data transfer command @@ -355,14 +358,11 @@ static void dw_mci_wait_while_busy(struct dw_mci *host, u32 cmd_flags) */ if ((cmd_flags & SDMMC_CMD_PRV_DAT_WAIT) && !(cmd_flags & SDMMC_CMD_VOLT_SWITCH)) { - while (mci_readl(host, STATUS) & SDMMC_STATUS_BUSY) { - if (time_after(jiffies, timeout)) { - /* Command will fail; we'll pass error then */ - dev_err(host->dev, "Busy; trying anyway\n"); - break; - } - udelay(10); - } + if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, + status, + !(status & SDMMC_STATUS_BUSY), + 10, 500 * USEC_PER_MSEC)) + dev_err(host->dev, "Busy; trying anyway\n"); } } @@ -555,7 +555,7 @@ static inline int dw_mci_prepare_desc64(struct dw_mci *host, { unsigned int desc_len; struct idmac_desc_64addr *desc_first, *desc_last, *desc; - unsigned long timeout; + u32 val; int i; desc_first = desc_last = desc = host->sg_cpu; @@ -577,12 +577,10 @@ static inline int dw_mci_prepare_desc64(struct dw_mci *host, * isn't still owned by IDMAC as IDMAC's write * ops and CPU's read ops are asynchronous. */ - timeout = jiffies + msecs_to_jiffies(100); - while (readl(&desc->des0) & IDMAC_DES0_OWN) { - if (time_after(jiffies, timeout)) - goto err_own_bit; - udelay(10); - } + if (readl_poll_timeout_atomic(&desc->des0, val, + !(val & IDMAC_DES0_OWN), + 10, 100 * USEC_PER_MSEC)) + goto err_own_bit; /* * Set the OWN bit and disable interrupts @@ -629,7 +627,7 @@ static inline int dw_mci_prepare_desc32(struct dw_mci *host, { unsigned int desc_len; struct idmac_desc *desc_first, *desc_last, *desc; - unsigned long timeout; + u32 val; int i; desc_first = desc_last = desc = host->sg_cpu; @@ -651,13 +649,11 @@ static inline int dw_mci_prepare_desc32(struct dw_mci *host, * isn't still owned by IDMAC as IDMAC's write * ops and CPU's read ops are asynchronous. */ - timeout = jiffies + msecs_to_jiffies(100); - while (readl(&desc->des0) & - cpu_to_le32(IDMAC_DES0_OWN)) { - if (time_after(jiffies, timeout)) - goto err_own_bit; - udelay(10); - } + if (readl_poll_timeout_atomic(&desc->des0, val, + IDMAC_OWN_CLR64(val), + 10, + 100 * USEC_PER_MSEC)) + goto err_own_bit; /* * Set the OWN bit and disable interrupts @@ -1136,7 +1132,6 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data) static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg) { struct dw_mci *host = slot->host; - unsigned long timeout = jiffies + msecs_to_jiffies(500); unsigned int cmd_status = 0; mci_writel(host, CMDARG, arg); @@ -1144,14 +1139,12 @@ static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg) dw_mci_wait_while_busy(host, cmd); mci_writel(host, CMD, SDMMC_CMD_START | cmd); - while (time_before(jiffies, timeout)) { - cmd_status = mci_readl(host, CMD); - if (!(cmd_status & SDMMC_CMD_START)) - return; - } - dev_err(&slot->mmc->class_dev, - "Timeout sending command (cmd %#x arg %#x status %#x)\n", - cmd, arg, cmd_status); + if (readl_poll_timeout_atomic(host->regs + SDMMC_CMD, cmd_status, + !(cmd_status & SDMMC_CMD_START), + 1, 500 * USEC_PER_MSEC)) + dev_err(&slot->mmc->class_dev, + "Timeout sending command (cmd %#x arg %#x status %#x)\n", + cmd, arg, cmd_status); } static void dw_mci_setup_bus(struct dw_mci_slot *slot, bool force_clkinit) @@ -2832,7 +2825,6 @@ no_dma: static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset) { - unsigned long timeout = jiffies + msecs_to_jiffies(500); u32 ctrl; ctrl = mci_readl(host, CTRL); @@ -2840,17 +2832,16 @@ static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset) mci_writel(host, CTRL, ctrl); /* wait till resets clear */ - do { - ctrl = mci_readl(host, CTRL); - if (!(ctrl & reset)) - return true; - } while (time_before(jiffies, timeout)); + if (readl_poll_timeout_atomic(host->regs + SDMMC_CTRL, ctrl, + !(ctrl & reset), + 1, 500 * USEC_PER_MSEC)) { + dev_err(host->dev, + "Timeout resetting block (ctrl reset %#x)\n", + ctrl & reset); + return false; + } - dev_err(host->dev, - "Timeout resetting block (ctrl reset %#x)\n", - ctrl & reset); - - return false; + return true; } static bool dw_mci_reset(struct dw_mci *host) @@ -2879,17 +2870,12 @@ static bool dw_mci_reset(struct dw_mci *host) /* if using dma we wait for dma_req to clear */ if (host->use_dma) { - unsigned long timeout = jiffies + msecs_to_jiffies(500); u32 status; - do { - status = mci_readl(host, STATUS); - if (!(status & SDMMC_STATUS_DMA_REQ)) - break; - cpu_relax(); - } while (time_before(jiffies, timeout)); - - if (status & SDMMC_STATUS_DMA_REQ) { + if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, + status, + !(status & SDMMC_STATUS_DMA_REQ), + 1, 500 * USEC_PER_MSEC)) { dev_err(host->dev, "%s: Timeout waiting for dma_req to clear during reset\n", __func__); From 4e7392b2ac6ed95b4ccacdec2f2e9e8e00c929c4 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 17 Feb 2017 10:56:40 +0800 Subject: [PATCH 002/148] mmc: dw_mmc: move dw_mci_reset forward to avoid declaration No functional change intended. Signed-off-by: Shawn Lin Signed-off-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 131 +++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 5372460d8bba..e057f557ffab 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -107,7 +107,6 @@ struct idmac_desc { /* Each descriptor can transfer up to 4KB of data in chained mode */ #define DW_MCI_DESC_DATA_LENGTH 0x1000 -static bool dw_mci_reset(struct dw_mci *host); static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset); static int dw_mci_card_busy(struct mmc_host *mmc); static int dw_mci_get_cd(struct mmc_host *mmc); @@ -1681,6 +1680,71 @@ static int dw_mci_prepare_hs400_tuning(struct mmc_host *mmc, return 0; } +static bool dw_mci_reset(struct dw_mci *host) +{ + u32 flags = SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET; + bool ret = false; + + /* + * Resetting generates a block interrupt, hence setting + * the scatter-gather pointer to NULL. + */ + if (host->sg) { + sg_miter_stop(&host->sg_miter); + host->sg = NULL; + } + + if (host->use_dma) + flags |= SDMMC_CTRL_DMA_RESET; + + if (dw_mci_ctrl_reset(host, flags)) { + /* + * In all cases we clear the RAWINTS register to clear any + * interrupts. + */ + mci_writel(host, RINTSTS, 0xFFFFFFFF); + + /* if using dma we wait for dma_req to clear */ + if (host->use_dma) { + u32 status; + + if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, + status, + !(status & SDMMC_STATUS_DMA_REQ), + 1, 500 * USEC_PER_MSEC)) { + dev_err(host->dev, + "%s: Timeout waiting for dma_req to clear during reset\n", + __func__); + goto ciu_out; + } + + /* when using DMA next we reset the fifo again */ + if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_FIFO_RESET)) + goto ciu_out; + } + } else { + /* if the controller reset bit did clear, then set clock regs */ + if (!(mci_readl(host, CTRL) & SDMMC_CTRL_RESET)) { + dev_err(host->dev, + "%s: fifo/dma reset bits didn't clear but ciu was reset, doing clock update\n", + __func__); + goto ciu_out; + } + } + + if (host->use_dma == TRANS_MODE_IDMAC) + /* It is also recommended that we reset and reprogram idmac */ + dw_mci_idmac_reset(host); + + ret = true; + +ciu_out: + /* After a CTRL reset we need to have CIU set clock registers */ + mci_send_cmd(host->cur_slot, SDMMC_CMD_UPD_CLK, 0); + + return ret; +} + static const struct mmc_host_ops dw_mci_ops = { .request = dw_mci_request, .pre_req = dw_mci_pre_req, @@ -2844,71 +2908,6 @@ static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset) return true; } -static bool dw_mci_reset(struct dw_mci *host) -{ - u32 flags = SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET; - bool ret = false; - - /* - * Reseting generates a block interrupt, hence setting - * the scatter-gather pointer to NULL. - */ - if (host->sg) { - sg_miter_stop(&host->sg_miter); - host->sg = NULL; - } - - if (host->use_dma) - flags |= SDMMC_CTRL_DMA_RESET; - - if (dw_mci_ctrl_reset(host, flags)) { - /* - * In all cases we clear the RAWINTS register to clear any - * interrupts. - */ - mci_writel(host, RINTSTS, 0xFFFFFFFF); - - /* if using dma we wait for dma_req to clear */ - if (host->use_dma) { - u32 status; - - if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, - status, - !(status & SDMMC_STATUS_DMA_REQ), - 1, 500 * USEC_PER_MSEC)) { - dev_err(host->dev, - "%s: Timeout waiting for dma_req to clear during reset\n", - __func__); - goto ciu_out; - } - - /* when using DMA next we reset the fifo again */ - if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_FIFO_RESET)) - goto ciu_out; - } - } else { - /* if the controller reset bit did clear, then set clock regs */ - if (!(mci_readl(host, CTRL) & SDMMC_CTRL_RESET)) { - dev_err(host->dev, - "%s: fifo/dma reset bits didn't clear but ciu was reset, doing clock update\n", - __func__); - goto ciu_out; - } - } - - if (host->use_dma == TRANS_MODE_IDMAC) - /* It is also recommended that we reset and reprogram idmac */ - dw_mci_idmac_reset(host); - - ret = true; - -ciu_out: - /* After a CTRL reset we need to have CIU set clock registers */ - mci_send_cmd(host->cur_slot, SDMMC_CMD_UPD_CLK, 0); - - return ret; -} - static void dw_mci_cmd11_timer(unsigned long arg) { struct dw_mci *host = (struct dw_mci *)arg; From 8e6db1f65510d57d666fe4d414550bb357c11e0e Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 17 Feb 2017 10:56:41 +0800 Subject: [PATCH 003/148] mmc: dw_mmc: move dw_mci_ctrl_reset forward to avoid declaration No functional change intended. Signed-off-by: Shawn Lin Signed-off-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 42 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index e057f557ffab..2914a1839928 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -107,7 +107,6 @@ struct idmac_desc { /* Each descriptor can transfer up to 4KB of data in chained mode */ #define DW_MCI_DESC_DATA_LENGTH 0x1000 -static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset); static int dw_mci_card_busy(struct mmc_host *mmc); static int dw_mci_get_cd(struct mmc_host *mmc); @@ -235,6 +234,26 @@ err: #endif /* defined(CONFIG_DEBUG_FS) */ static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg); +static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset) +{ + u32 ctrl; + + ctrl = mci_readl(host, CTRL); + ctrl |= reset; + mci_writel(host, CTRL, ctrl); + + /* wait till resets clear */ + if (readl_poll_timeout_atomic(host->regs + SDMMC_CTRL, ctrl, + !(ctrl & reset), + 1, 500 * USEC_PER_MSEC)) { + dev_err(host->dev, + "Timeout resetting block (ctrl reset %#x)\n", + ctrl & reset); + return false; + } + + return true; +} static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd) { @@ -2887,27 +2906,6 @@ no_dma: host->use_dma = TRANS_MODE_PIO; } -static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset) -{ - u32 ctrl; - - ctrl = mci_readl(host, CTRL); - ctrl |= reset; - mci_writel(host, CTRL, ctrl); - - /* wait till resets clear */ - if (readl_poll_timeout_atomic(host->regs + SDMMC_CTRL, ctrl, - !(ctrl & reset), - 1, 500 * USEC_PER_MSEC)) { - dev_err(host->dev, - "Timeout resetting block (ctrl reset %#x)\n", - ctrl & reset); - return false; - } - - return true; -} - static void dw_mci_cmd11_timer(unsigned long arg) { struct dw_mci *host = (struct dw_mci *)arg; From 671fa142fc034b0fb5c067afe8f0bbc64979f074 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 17 Feb 2017 10:56:42 +0800 Subject: [PATCH 004/148] mmc: dw_mmc: move dw_mci_get_cd forward to avoid declaration No functional change intended. Signed-off-by: Shawn Lin Signed-off-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 83 +++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 2914a1839928..0eace317d34f 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -108,7 +108,6 @@ struct idmac_desc { #define DW_MCI_DESC_DATA_LENGTH 0x1000 static int dw_mci_card_busy(struct mmc_host *mmc); -static int dw_mci_get_cd(struct mmc_host *mmc); #if defined(CONFIG_DEBUG_FS) static int dw_mci_req_show(struct seq_file *s, void *v) @@ -934,6 +933,47 @@ static void dw_mci_post_req(struct mmc_host *mmc, data->host_cookie = COOKIE_UNMAPPED; } +static int dw_mci_get_cd(struct mmc_host *mmc) +{ + int present; + struct dw_mci_slot *slot = mmc_priv(mmc); + struct dw_mci *host = slot->host; + int gpio_cd = mmc_gpio_get_cd(mmc); + + /* Use platform get_cd function, else try onboard card detect */ + if (((mmc->caps & MMC_CAP_NEEDS_POLL) + || !mmc_card_is_removable(mmc))) { + present = 1; + + if (!test_bit(DW_MMC_CARD_PRESENT, &slot->flags)) { + if (mmc->caps & MMC_CAP_NEEDS_POLL) { + dev_info(&mmc->class_dev, + "card is polling.\n"); + } else { + dev_info(&mmc->class_dev, + "card is non-removable.\n"); + } + set_bit(DW_MMC_CARD_PRESENT, &slot->flags); + } + + return present; + } else if (gpio_cd >= 0) + present = gpio_cd; + else + present = (mci_readl(slot->host, CDETECT) & (1 << slot->id)) + == 0 ? 1 : 0; + + spin_lock_bh(&host->lock); + if (present && !test_and_set_bit(DW_MMC_CARD_PRESENT, &slot->flags)) + dev_dbg(&mmc->class_dev, "card is present\n"); + else if (!present && + !test_and_clear_bit(DW_MMC_CARD_PRESENT, &slot->flags)) + dev_dbg(&mmc->class_dev, "card is not present\n"); + spin_unlock_bh(&host->lock); + + return present; +} + static void dw_mci_adjust_fifoth(struct dw_mci *host, struct mmc_data *data) { unsigned int blksz = data->blksz; @@ -1545,47 +1585,6 @@ static int dw_mci_get_ro(struct mmc_host *mmc) return read_only; } -static int dw_mci_get_cd(struct mmc_host *mmc) -{ - int present; - struct dw_mci_slot *slot = mmc_priv(mmc); - struct dw_mci *host = slot->host; - int gpio_cd = mmc_gpio_get_cd(mmc); - - /* Use platform get_cd function, else try onboard card detect */ - if (((mmc->caps & MMC_CAP_NEEDS_POLL) - || !mmc_card_is_removable(mmc))) { - present = 1; - - if (!test_bit(DW_MMC_CARD_PRESENT, &slot->flags)) { - if (mmc->caps & MMC_CAP_NEEDS_POLL) { - dev_info(&mmc->class_dev, - "card is polling.\n"); - } else { - dev_info(&mmc->class_dev, - "card is non-removable.\n"); - } - set_bit(DW_MMC_CARD_PRESENT, &slot->flags); - } - - return present; - } else if (gpio_cd >= 0) - present = gpio_cd; - else - present = (mci_readl(slot->host, CDETECT) & (1 << slot->id)) - == 0 ? 1 : 0; - - spin_lock_bh(&host->lock); - if (present && !test_and_set_bit(DW_MMC_CARD_PRESENT, &slot->flags)) - dev_dbg(&mmc->class_dev, "card is present\n"); - else if (!present && - !test_and_clear_bit(DW_MMC_CARD_PRESENT, &slot->flags)) - dev_dbg(&mmc->class_dev, "card is not present\n"); - spin_unlock_bh(&host->lock); - - return present; -} - static void dw_mci_hw_reset(struct mmc_host *mmc) { struct dw_mci_slot *slot = mmc_priv(mmc); From 132b3b6aa61864ef9f4fa21e88f2e9dab6caeec3 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 17 Feb 2017 10:59:35 +0800 Subject: [PATCH 005/148] mmc: dw_mmc: remove declaration of dw_mci_card_busy No need to declar it there, remove it. Signed-off-by: Shawn Lin Signed-off-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 0eace317d34f..1d67a0a69521 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -107,8 +107,6 @@ struct idmac_desc { /* Each descriptor can transfer up to 4KB of data in chained mode */ #define DW_MCI_DESC_DATA_LENGTH 0x1000 -static int dw_mci_card_busy(struct mmc_host *mmc); - #if defined(CONFIG_DEBUG_FS) static int dw_mci_req_show(struct seq_file *s, void *v) { From 4dba18defb4c7bbea8af85913cd2dc506178389a Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 17 Feb 2017 10:59:44 +0800 Subject: [PATCH 006/148] mmc: dw_mmc: move mci_send_cmd forward to avoid declaration No functional change intended. Signed-off-by: Shawn Lin Signed-off-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 81 +++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 1d67a0a69521..0c641fb0fe59 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -230,7 +230,6 @@ err: } #endif /* defined(CONFIG_DEBUG_FS) */ -static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg); static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset) { u32 ctrl; @@ -252,6 +251,46 @@ static bool dw_mci_ctrl_reset(struct dw_mci *host, u32 reset) return true; } +static void dw_mci_wait_while_busy(struct dw_mci *host, u32 cmd_flags) +{ + u32 status; + + /* + * Databook says that before issuing a new data transfer command + * we need to check to see if the card is busy. Data transfer commands + * all have SDMMC_CMD_PRV_DAT_WAIT set, so we'll key off that. + * + * ...also allow sending for SDMMC_CMD_VOLT_SWITCH where busy is + * expected. + */ + if ((cmd_flags & SDMMC_CMD_PRV_DAT_WAIT) && + !(cmd_flags & SDMMC_CMD_VOLT_SWITCH)) { + if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, + status, + !(status & SDMMC_STATUS_BUSY), + 10, 500 * USEC_PER_MSEC)) + dev_err(host->dev, "Busy; trying anyway\n"); + } +} + +static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg) +{ + struct dw_mci *host = slot->host; + unsigned int cmd_status = 0; + + mci_writel(host, CMDARG, arg); + wmb(); /* drain writebuffer */ + dw_mci_wait_while_busy(host, cmd); + mci_writel(host, CMD, SDMMC_CMD_START | cmd); + + if (readl_poll_timeout_atomic(host->regs + SDMMC_CMD, cmd_status, + !(cmd_status & SDMMC_CMD_START), + 1, 500 * USEC_PER_MSEC)) + dev_err(&slot->mmc->class_dev, + "Timeout sending command (cmd %#x arg %#x status %#x)\n", + cmd, arg, cmd_status); +} + static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd) { struct dw_mci_slot *slot = mmc_priv(mmc); @@ -359,28 +398,6 @@ static u32 dw_mci_prep_stop_abort(struct dw_mci *host, struct mmc_command *cmd) return cmdr; } -static void dw_mci_wait_while_busy(struct dw_mci *host, u32 cmd_flags) -{ - u32 status; - - /* - * Databook says that before issuing a new data transfer command - * we need to check to see if the card is busy. Data transfer commands - * all have SDMMC_CMD_PRV_DAT_WAIT set, so we'll key off that. - * - * ...also allow sending for SDMMC_CMD_VOLT_SWITCH where busy is - * expected. - */ - if ((cmd_flags & SDMMC_CMD_PRV_DAT_WAIT) && - !(cmd_flags & SDMMC_CMD_VOLT_SWITCH)) { - if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, - status, - !(status & SDMMC_STATUS_BUSY), - 10, 500 * USEC_PER_MSEC)) - dev_err(host->dev, "Busy; trying anyway\n"); - } -} - static void dw_mci_start_command(struct dw_mci *host, struct mmc_command *cmd, u32 cmd_flags) { @@ -1185,24 +1202,6 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data) } } -static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg) -{ - struct dw_mci *host = slot->host; - unsigned int cmd_status = 0; - - mci_writel(host, CMDARG, arg); - wmb(); /* drain writebuffer */ - dw_mci_wait_while_busy(host, cmd); - mci_writel(host, CMD, SDMMC_CMD_START | cmd); - - if (readl_poll_timeout_atomic(host->regs + SDMMC_CMD, cmd_status, - !(cmd_status & SDMMC_CMD_START), - 1, 500 * USEC_PER_MSEC)) - dev_err(&slot->mmc->class_dev, - "Timeout sending command (cmd %#x arg %#x status %#x)\n", - cmd, arg, cmd_status); -} - static void dw_mci_setup_bus(struct dw_mci_slot *slot, bool force_clkinit) { struct dw_mci *host = slot->host; From bc2dcc1a2a5e3e796b71f742d6d241ba0523739b Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 17 Feb 2017 10:59:52 +0800 Subject: [PATCH 007/148] mmc: dw_mmc: improve dw_mci_reset a bit Too much condition iteration makes the code less readable. Slightly improve it. Signed-off-by: Shawn Lin Signed-off-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 40 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 0c641fb0fe59..d0f0f658e741 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1699,6 +1699,7 @@ static bool dw_mci_reset(struct dw_mci *host) { u32 flags = SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET; bool ret = false; + u32 status = 0; /* * Resetting generates a block interrupt, hence setting @@ -1714,29 +1715,30 @@ static bool dw_mci_reset(struct dw_mci *host) if (dw_mci_ctrl_reset(host, flags)) { /* - * In all cases we clear the RAWINTS register to clear any - * interrupts. + * In all cases we clear the RAWINTS + * register to clear any interrupts. */ mci_writel(host, RINTSTS, 0xFFFFFFFF); - /* if using dma we wait for dma_req to clear */ - if (host->use_dma) { - u32 status; - - if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, - status, - !(status & SDMMC_STATUS_DMA_REQ), - 1, 500 * USEC_PER_MSEC)) { - dev_err(host->dev, - "%s: Timeout waiting for dma_req to clear during reset\n", - __func__); - goto ciu_out; - } - - /* when using DMA next we reset the fifo again */ - if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_FIFO_RESET)) - goto ciu_out; + if (!host->use_dma) { + ret = true; + goto ciu_out; } + + /* Wait for dma_req to be cleared */ + if (readl_poll_timeout_atomic(host->regs + SDMMC_STATUS, + status, + !(status & SDMMC_STATUS_DMA_REQ), + 1, 500 * USEC_PER_MSEC)) { + dev_err(host->dev, + "%s: Timeout waiting for dma_req to be cleared\n", + __func__); + goto ciu_out; + } + + /* when using DMA next we reset the fifo again */ + if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_FIFO_RESET)) + goto ciu_out; } else { /* if the controller reset bit did clear, then set clock regs */ if (!(mci_readl(host, CTRL) & SDMMC_CTRL_RESET)) { From 94d765bbfb1910ef8db5699d19907a0756a99d03 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:19:23 +0100 Subject: [PATCH 008/148] mmc: meson-gx: simplify bounce buffer setting in meson_mmc_start_cmd Core ensures that there are no commands with cmd->data being set and nothing to transfer. And we don't have to reset bit CMD_CFG_DATA_NUM because cmd_cfg was zero-initialized and this bit isn't set. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 5a959783304b..07a7399cf69e 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -516,14 +516,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) desc->cmd_cfg &= ~CMD_CFG_DATA_WR; } - if (xfer_bytes > 0) { - desc->cmd_cfg &= ~CMD_CFG_DATA_NUM; - desc->cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; - } else { - /* write data to data_addr */ - desc->cmd_cfg |= CMD_CFG_DATA_NUM; - desc->cmd_data = 0; - } + desc->cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; cmd_cfg_timeout = 12; } else { From 3d6c991b3dc9b632de5623adf980ecec399008f3 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:20:44 +0100 Subject: [PATCH 009/148] mmc: meson-gx: make two functions return void The return value of meson_mmc_request_done and meson_mmc_read_resp isn't used, so make both functions return void. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 07a7399cf69e..b99461ff61d8 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -426,7 +426,8 @@ static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) __func__, orig, val); } -static int meson_mmc_request_done(struct mmc_host *mmc, struct mmc_request *mrq) +static void meson_mmc_request_done(struct mmc_host *mmc, + struct mmc_request *mrq) { struct meson_host *host = mmc_priv(mmc); @@ -435,8 +436,6 @@ static int meson_mmc_request_done(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = NULL; host->cmd = NULL; mmc_request_done(host->mmc, mrq); - - return 0; } static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) @@ -554,7 +553,7 @@ static void meson_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) meson_mmc_start_cmd(mmc, mrq->cmd); } -static int meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd) +static void meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd) { struct meson_host *host = mmc_priv(mmc); @@ -566,8 +565,6 @@ static int meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd) } else if (cmd->flags & MMC_RSP_PRESENT) { cmd->resp[0] = readl(host->regs + SD_EMMC_CMD_RSP); } - - return 0; } static irqreturn_t meson_mmc_irq(int irq, void *dev_id) From 9a1da4df8502dd658db850650137d401b1fe123d Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:21:54 +0100 Subject: [PATCH 010/148] mmc: meson-gx: remove unused members irq, ocr_mask from struct meson_host Member ocr_mask is never used and member irq we can replace with a local variable in meson_mmc_probe. So let's remove both members. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index b99461ff61d8..b9de3d4714bb 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -127,8 +127,6 @@ struct meson_host { spinlock_t lock; void __iomem *regs; - int irq; - u32 ocr_mask; struct clk *core_clk; struct clk_mux mux; struct clk *mux_clk; @@ -712,7 +710,7 @@ static int meson_mmc_probe(struct platform_device *pdev) struct resource *res; struct meson_host *host; struct mmc_host *mmc; - int ret; + int ret, irq; mmc = mmc_alloc_host(sizeof(struct meson_host), &pdev->dev); if (!mmc) @@ -744,8 +742,8 @@ static int meson_mmc_probe(struct platform_device *pdev) goto free_host; } - host->irq = platform_get_irq(pdev, 0); - if (host->irq == 0) { + irq = platform_get_irq(pdev, 0); + if (!irq) { dev_err(&pdev->dev, "failed to get interrupt resource.\n"); ret = -EINVAL; goto free_host; @@ -773,9 +771,9 @@ static int meson_mmc_probe(struct platform_device *pdev) writel(IRQ_EN_MASK, host->regs + SD_EMMC_STATUS); writel(IRQ_EN_MASK, host->regs + SD_EMMC_IRQ_EN); - ret = devm_request_threaded_irq(&pdev->dev, host->irq, - meson_mmc_irq, meson_mmc_irq_thread, - IRQF_SHARED, DRIVER_NAME, host); + ret = devm_request_threaded_irq(&pdev->dev, irq, meson_mmc_irq, + meson_mmc_irq_thread, IRQF_SHARED, + DRIVER_NAME, host); if (ret) goto free_host; From 7558c11378dd0ff1525a0cca5180804f6428af20 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:22:57 +0100 Subject: [PATCH 011/148] mmc: meson-gx: remove unneeded variable in meson_mmc_clk_init Because the DT requires a fixed number of mux parent clocks, variable mux_parent_count can be replaced with constant MUX_CLK_NUM_PARENTS, so remove it. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index b9de3d4714bb..ea2db3cea4ad 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -242,7 +242,6 @@ static int meson_mmc_clk_init(struct meson_host *host) char clk_name[32]; int i, ret = 0; const char *mux_parent_names[MUX_CLK_NUM_PARENTS]; - unsigned int mux_parent_count = 0; const char *clk_div_parents[1]; u32 clk_reg, cfg; @@ -261,7 +260,6 @@ static int meson_mmc_clk_init(struct meson_host *host) } mux_parent_names[i] = __clk_get_name(host->mux_parent[i]); - mux_parent_count++; } /* create the mux */ @@ -270,7 +268,7 @@ static int meson_mmc_clk_init(struct meson_host *host) init.ops = &clk_mux_ops; init.flags = 0; init.parent_names = mux_parent_names; - init.num_parents = mux_parent_count; + init.num_parents = MUX_CLK_NUM_PARENTS; host->mux.reg = host->regs + SD_EMMC_CLOCK; host->mux.shift = CLK_SRC_SHIFT; From e9883ef228e5c128d34143ae87c97f555f79620c Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:24:09 +0100 Subject: [PATCH 012/148] mmc: meson-gx: remove member parent_mux from struct meson_host Member mux_parent isn't used outside meson_mmc_clk_init. So remove it and replace it with a local variable in meson_mmc_clk_init. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index ea2db3cea4ad..68e76fa8052b 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -130,7 +130,6 @@ struct meson_host { struct clk *core_clk; struct clk_mux mux; struct clk *mux_clk; - struct clk *mux_parent[MUX_CLK_NUM_PARENTS]; unsigned long current_clock; struct clk_divider cfg_div; @@ -247,19 +246,18 @@ static int meson_mmc_clk_init(struct meson_host *host) /* get the mux parents */ for (i = 0; i < MUX_CLK_NUM_PARENTS; i++) { + struct clk *clk; char name[16]; snprintf(name, sizeof(name), "clkin%d", i); - host->mux_parent[i] = devm_clk_get(host->dev, name); - if (IS_ERR(host->mux_parent[i])) { - ret = PTR_ERR(host->mux_parent[i]); - if (PTR_ERR(host->mux_parent[i]) != -EPROBE_DEFER) + clk = devm_clk_get(host->dev, name); + if (IS_ERR(clk)) { + if (clk != ERR_PTR(-EPROBE_DEFER)) dev_err(host->dev, "Missing clock %s\n", name); - host->mux_parent[i] = NULL; - return ret; + return PTR_ERR(clk); } - mux_parent_names[i] = __clk_get_name(host->mux_parent[i]); + mux_parent_names[i] = __clk_get_name(clk); } /* create the mux */ From cac3a47855c24bf060e58977473747ef3de7dedf Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:25:14 +0100 Subject: [PATCH 013/148] mmc: meson-gx: fix error path in meson_mmc_clk_init / meson_mmc_probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The condition should be "if (ret)" as the disable/unprepare is supposed to be executed if the previous command fails. In addition adjust the error path in probe to properly deal with the case that cfg_div_clk can be registered successfully but enable/prepare fails. In this case we shouldn't call clk_disable_unprepare. Reported-by: Michał Zegan Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 68e76fa8052b..002e4aac7ca9 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -321,7 +321,7 @@ static int meson_mmc_clk_init(struct meson_host *host) host->mmc->f_min = clk_round_rate(host->cfg_div_clk, 400000); ret = meson_mmc_clk_set(host, host->mmc->f_min); - if (!ret) + if (ret) clk_disable_unprepare(host->cfg_div_clk); return ret; @@ -771,7 +771,7 @@ static int meson_mmc_probe(struct platform_device *pdev) meson_mmc_irq_thread, IRQF_SHARED, DRIVER_NAME, host); if (ret) - goto free_host; + goto err_div_clk; mmc->max_blk_count = CMD_CFG_LENGTH_MASK; mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size; @@ -784,7 +784,7 @@ static int meson_mmc_probe(struct platform_device *pdev) if (host->bounce_buf == NULL) { dev_err(host->dev, "Unable to map allocate DMA bounce buffer.\n"); ret = -ENOMEM; - goto free_host; + goto err_div_clk; } mmc->ops = &meson_mmc_ops; @@ -792,8 +792,9 @@ static int meson_mmc_probe(struct platform_device *pdev) return 0; -free_host: +err_div_clk: clk_disable_unprepare(host->cfg_div_clk); +free_host: clk_disable_unprepare(host->core_clk); mmc_free_host(mmc); return ret; From 7b9ebad38fe64644714826966c9a164d90d1c408 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:26:24 +0100 Subject: [PATCH 014/148] mmc: meson-gx: remove unneeded devm_kstrdup in meson_mmc_clk_init CLK core does a deep copy of init.name, therefore it's fully ok to provide a local variable. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 002e4aac7ca9..684cc08813ac 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -281,7 +281,7 @@ static int meson_mmc_clk_init(struct meson_host *host) /* create the divider */ snprintf(clk_name, sizeof(clk_name), "%s#div", dev_name(host->dev)); - init.name = devm_kstrdup(host->dev, clk_name, GFP_KERNEL); + init.name = clk_name; init.ops = &clk_divider_ops; init.flags = CLK_SET_RATE_PARENT; clk_div_parents[0] = __clk_get_name(host->mux_clk); From c01d1219ace5768c18eb33f28d06937819f65b13 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:35:13 +0100 Subject: [PATCH 015/148] mmc: meson-gx: improve initial configuration Config values which are not changed during runtime we can set in the probe function already. The block size setting is overwritten later in meson_mmc_start_cmd anyway if needed, so it doesn't harm if we remove this setting in meson_mmc_set_ios. In addition write config register only if configuration changed. Don't change the location of clock initialization as in an earlier version of the patch, this change causes a hang. This issue was reported and fix suggested by: Helmut Klein Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 684cc08813ac..84553c23ed85 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -394,15 +394,6 @@ static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) val &= ~(CFG_BUS_WIDTH_MASK << CFG_BUS_WIDTH_SHIFT); val |= bus_width << CFG_BUS_WIDTH_SHIFT; - val &= ~(CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT); - val |= ilog2(SD_EMMC_CFG_BLK_SIZE) << CFG_BLK_LEN_SHIFT; - - val &= ~(CFG_RESP_TIMEOUT_MASK << CFG_RESP_TIMEOUT_SHIFT); - val |= ilog2(SD_EMMC_CFG_RESP_TIMEOUT) << CFG_RESP_TIMEOUT_SHIFT; - - val &= ~(CFG_RC_CC_MASK << CFG_RC_CC_SHIFT); - val |= ilog2(SD_EMMC_CFG_CMD_GAP) << CFG_RC_CC_SHIFT; - val &= ~CFG_DDR; if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52 || @@ -413,11 +404,11 @@ static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) if (ios->timing == MMC_TIMING_MMC_HS400) val |= CFG_CHK_DS; - writel(val, host->regs + SD_EMMC_CFG); - - if (val != orig) + if (val != orig) { + writel(val, host->regs + SD_EMMC_CFG); dev_dbg(host->dev, "%s: SD_EMMC_CFG: 0x%08x -> 0x%08x\n", __func__, orig, val); + } } static void meson_mmc_request_done(struct mmc_host *mmc, @@ -695,6 +686,17 @@ static int meson_mmc_get_cd(struct mmc_host *mmc) return status; } +static void meson_mmc_cfg_init(struct meson_host *host) +{ + u32 cfg = 0; + + cfg |= ilog2(SD_EMMC_CFG_RESP_TIMEOUT) << CFG_RESP_TIMEOUT_SHIFT; + cfg |= ilog2(SD_EMMC_CFG_CMD_GAP) << CFG_RC_CC_SHIFT; + cfg |= ilog2(SD_EMMC_CFG_BLK_SIZE) << CFG_BLK_LEN_SHIFT; + + writel(cfg, host->regs + SD_EMMC_CFG); +} + static const struct mmc_host_ops meson_mmc_ops = { .request = meson_mmc_request, .set_ios = meson_mmc_set_ios, @@ -767,6 +769,9 @@ static int meson_mmc_probe(struct platform_device *pdev) writel(IRQ_EN_MASK, host->regs + SD_EMMC_STATUS); writel(IRQ_EN_MASK, host->regs + SD_EMMC_IRQ_EN); + /* set config to sane default */ + meson_mmc_cfg_init(host); + ret = devm_request_threaded_irq(&pdev->dev, irq, meson_mmc_irq, meson_mmc_irq_thread, IRQF_SHARED, DRIVER_NAME, host); From 7cdcc480e4c32b0480233adb196a573ef538a6b6 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:36:45 +0100 Subject: [PATCH 016/148] mmc: meson-gx: remove member mrq from struct meson_host Struct mmc_command includes a reference to the related mmc_request. Therefore we don't have to store mrq separately in struct meson_host. And we can remove some now unneeded WARN_ON's. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 84553c23ed85..d1826cf87a76 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -122,7 +122,6 @@ struct meson_host { struct device *dev; struct mmc_host *mmc; - struct mmc_request *mrq; struct mmc_command *cmd; spinlock_t lock; @@ -416,9 +415,6 @@ static void meson_mmc_request_done(struct mmc_host *mmc, { struct meson_host *host = mmc_priv(mmc); - WARN_ON(host->mrq != mrq); - - host->mrq = NULL; host->cmd = NULL; mmc_request_done(host->mmc, mrq); } @@ -525,13 +521,9 @@ static void meson_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct meson_host *host = mmc_priv(mmc); - WARN_ON(host->mrq != NULL); - /* Stop execution */ writel(0, host->regs + SD_EMMC_START); - host->mrq = mrq; - if (mrq->sbc) meson_mmc_start_cmd(mmc, mrq->sbc); else @@ -555,7 +547,6 @@ static void meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd) static irqreturn_t meson_mmc_irq(int irq, void *dev_id) { struct meson_host *host = dev_id; - struct mmc_request *mrq; struct mmc_command *cmd; u32 irq_en, status, raw_status; irqreturn_t ret = IRQ_HANDLED; @@ -565,11 +556,6 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id) cmd = host->cmd; - mrq = host->mrq; - - if (WARN_ON(!mrq)) - return IRQ_NONE; - if (WARN_ON(!cmd)) return IRQ_NONE; @@ -616,7 +602,7 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id) else { dev_warn(host->dev, "Unknown IRQ! status=0x%04x: MMC CMD%u arg=0x%08x flags=0x%08x stop=%d\n", status, cmd->opcode, cmd->arg, - cmd->flags, mrq->stop ? 1 : 0); + cmd->flags, cmd->mrq->stop ? 1 : 0); if (cmd->data) { struct mmc_data *data = cmd->data; @@ -643,14 +629,10 @@ out: static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) { struct meson_host *host = dev_id; - struct mmc_request *mrq = host->mrq; struct mmc_command *cmd = host->cmd; struct mmc_data *data; unsigned int xfer_bytes; - if (WARN_ON(!mrq)) - return IRQ_NONE; - if (WARN_ON(!cmd)) return IRQ_NONE; @@ -664,8 +646,8 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) } meson_mmc_read_resp(host->mmc, cmd); - if (!data || !data->stop || mrq->sbc) - meson_mmc_request_done(host->mmc, mrq); + if (!data || !data->stop || cmd->mrq->sbc) + meson_mmc_request_done(host->mmc, cmd->mrq); else meson_mmc_start_cmd(host->mmc, data->stop); From bb11eff1f1820b9f3bf6578af18b3d18094e7f54 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 4 Mar 2017 13:37:46 +0100 Subject: [PATCH 017/148] mmc: meson-gx: replace magic timeout numbers with constants Replace timeout magic numbers with proper constants. Signed-off-by: Heiner Kallweit Acked-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index d1826cf87a76..62597638ccaf 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -116,6 +116,8 @@ #define SD_EMMC_CFG_BLK_SIZE 512 /* internal buffer max: 512 bytes */ #define SD_EMMC_CFG_RESP_TIMEOUT 256 /* in clock cycles */ +#define SD_EMMC_CMD_TIMEOUT 1024 /* in ms */ +#define SD_EMMC_CMD_TIMEOUT_DATA 4096 /* in ms */ #define SD_EMMC_CFG_CMD_GAP 16 /* in clock cycles */ #define MUX_CLK_NUM_PARENTS 2 @@ -498,10 +500,10 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) desc->cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; - cmd_cfg_timeout = 12; + cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT_DATA); } else { desc->cmd_cfg &= ~CMD_CFG_DATA_IO; - cmd_cfg_timeout = 10; + cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT); } desc->cmd_cfg |= (cmd_cfg_timeout & CMD_CFG_TIMEOUT_MASK) << CMD_CFG_TIMEOUT_SHIFT; From 21b22284619bbb201775c125af53f2834eb91674 Mon Sep 17 00:00:00 2001 From: Alexey Khoroshilov Date: Sat, 11 Feb 2017 01:54:46 +0300 Subject: [PATCH 018/148] mmc: sdhci-pxav2: add error handling of clk_prepare_enable() There is no check if clk_prepare_enable() succeed in sdhci_pxav2_probe(). Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Alexey Khoroshilov Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pxav2.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c index 347eae2d7b6a..80513f54e9d4 100644 --- a/drivers/mmc/host/sdhci-pxav2.c +++ b/drivers/mmc/host/sdhci-pxav2.c @@ -185,7 +185,11 @@ static int sdhci_pxav2_probe(struct platform_device *pdev) goto err_clk_get; } pltfm_host->clk = clk; - clk_prepare_enable(clk); + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable io clock\n"); + goto err_clk_enable; + } host->quirks = SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL @@ -228,6 +232,7 @@ static int sdhci_pxav2_probe(struct platform_device *pdev) err_add_host: clk_disable_unprepare(clk); +err_clk_enable: clk_put(clk); err_clk_get: sdhci_pltfm_free(pdev); From 52ad9a8e854ca13151f4af8140297f73d49e318a Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Fri, 17 Feb 2017 19:22:41 +0100 Subject: [PATCH 019/148] mmc: tmio: ensure end of DMA and SD access are in sync The current code assumes that DMA is finished before SD access end is flagged. Thus, it schedules the 'dma_complete' tasklet in the SD card interrupt routine when DATAEND is set. The assumption is not safe, though. Even by mounting an SD card, it can be seen that sometimes DMA complete is first, sometimes DATAEND. It seems they are usually close enough timewise to not cause problems. However, a customer reported that with CMD53 sometimes things really break apart. As a result, the BSP has a patch which introduces flags for both events and makes sure both flags are set before scheduling the tasklet. The customer accepted the patch, yet it doesn't seem a proper upstream solution to me. This patch refactors the code to replace the tasklet with already existing and more lightweight mechanisms. First of all, we set the callback in a DMA descriptor to automatically get notified when DMA is done. In the callback, we then use a completion to make sure the SD access has already ended. Then, we proceed as before. Signed-off-by: Wolfram Sang Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc.h | 2 +- drivers/mmc/host/tmio_mmc_dma.c | 58 +++++++++++++++++++-------------- drivers/mmc/host/tmio_mmc_pio.c | 4 +-- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index 2b349d48fb9a..891d400d2a7c 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -137,7 +137,7 @@ struct tmio_mmc_host { bool force_pio; struct dma_chan *chan_rx; struct dma_chan *chan_tx; - struct tasklet_struct dma_complete; + struct completion dma_dataend; struct tasklet_struct dma_issue; struct scatterlist bounce_sg; u8 *bounce_buf; diff --git a/drivers/mmc/host/tmio_mmc_dma.c b/drivers/mmc/host/tmio_mmc_dma.c index fa8a936a3d9b..c7684fa91f1f 100644 --- a/drivers/mmc/host/tmio_mmc_dma.c +++ b/drivers/mmc/host/tmio_mmc_dma.c @@ -43,6 +43,31 @@ void tmio_mmc_abort_dma(struct tmio_mmc_host *host) tmio_mmc_enable_dma(host, true); } +static void tmio_mmc_dma_callback(void *arg) +{ + struct tmio_mmc_host *host = arg; + + wait_for_completion(&host->dma_dataend); + + spin_lock_irq(&host->lock); + + if (!host->data) + goto out; + + if (host->data->flags & MMC_DATA_READ) + dma_unmap_sg(host->chan_rx->device->dev, + host->sg_ptr, host->sg_len, + DMA_FROM_DEVICE); + else + dma_unmap_sg(host->chan_tx->device->dev, + host->sg_ptr, host->sg_len, + DMA_TO_DEVICE); + + tmio_mmc_do_data_irq(host); +out: + spin_unlock_irq(&host->lock); +} + static void tmio_mmc_start_dma_rx(struct tmio_mmc_host *host) { struct scatterlist *sg = host->sg_ptr, *sg_tmp; @@ -88,6 +113,10 @@ static void tmio_mmc_start_dma_rx(struct tmio_mmc_host *host) DMA_DEV_TO_MEM, DMA_CTRL_ACK); if (desc) { + reinit_completion(&host->dma_dataend); + desc->callback = tmio_mmc_dma_callback; + desc->callback_param = host; + cookie = dmaengine_submit(desc); if (cookie < 0) { desc = NULL; @@ -162,6 +191,10 @@ static void tmio_mmc_start_dma_tx(struct tmio_mmc_host *host) DMA_MEM_TO_DEV, DMA_CTRL_ACK); if (desc) { + reinit_completion(&host->dma_dataend); + desc->callback = tmio_mmc_dma_callback; + desc->callback_param = host; + cookie = dmaengine_submit(desc); if (cookie < 0) { desc = NULL; @@ -221,29 +254,6 @@ static void tmio_mmc_issue_tasklet_fn(unsigned long priv) dma_async_issue_pending(chan); } -static void tmio_mmc_tasklet_fn(unsigned long arg) -{ - struct tmio_mmc_host *host = (struct tmio_mmc_host *)arg; - - spin_lock_irq(&host->lock); - - if (!host->data) - goto out; - - if (host->data->flags & MMC_DATA_READ) - dma_unmap_sg(host->chan_rx->device->dev, - host->sg_ptr, host->sg_len, - DMA_FROM_DEVICE); - else - dma_unmap_sg(host->chan_tx->device->dev, - host->sg_ptr, host->sg_len, - DMA_TO_DEVICE); - - tmio_mmc_do_data_irq(host); -out: - spin_unlock_irq(&host->lock); -} - void tmio_mmc_request_dma(struct tmio_mmc_host *host, struct tmio_mmc_data *pdata) { /* We can only either use DMA for both Tx and Rx or not use it at all */ @@ -306,7 +316,7 @@ void tmio_mmc_request_dma(struct tmio_mmc_host *host, struct tmio_mmc_data *pdat if (!host->bounce_buf) goto ebouncebuf; - tasklet_init(&host->dma_complete, tmio_mmc_tasklet_fn, (unsigned long)host); + init_completion(&host->dma_dataend); tasklet_init(&host->dma_issue, tmio_mmc_issue_tasklet_fn, (unsigned long)host); } diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index 6b789a739d4d..c41f2252945e 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -596,11 +596,11 @@ static void tmio_mmc_data_irq(struct tmio_mmc_host *host, unsigned int stat) if (done) { tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_DATAEND); - tasklet_schedule(&host->dma_complete); + complete(&host->dma_dataend); } } else if (host->chan_rx && (data->flags & MMC_DATA_READ) && !host->force_pio) { tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_DATAEND); - tasklet_schedule(&host->dma_complete); + complete(&host->dma_dataend); } else { tmio_mmc_do_data_irq(host); tmio_mmc_disable_mmc_irqs(host, TMIO_MASK_READOP | TMIO_MASK_WRITEOP); From 9afcbf4a6f1995f3b47088764eaef7b56154beb9 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 14 Mar 2017 11:09:16 +0100 Subject: [PATCH 020/148] mmc: host: tmio: use defines for CTL_STOP_INTERNAL_ACTION values Reviewed-by: Simon Horman Signed-off-by: Wolfram Sang Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc.h | 4 ++++ drivers/mmc/host/tmio_mmc_pio.c | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index 891d400d2a7c..a484ed87bcd0 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -50,6 +50,10 @@ #define CTL_CLK_AND_WAIT_CTL 0x138 #define CTL_RESET_SDIO 0x1e0 +/* Definitions for values the CTL_STOP_INTERNAL_ACTION register can take */ +#define TMIO_STOP_STP BIT(0) +#define TMIO_STOP_SEC BIT(8) + /* Definitions for values the CTRL_STATUS register can take. */ #define TMIO_STAT_CMDRESPEND BIT(0) #define TMIO_STAT_DATAEND BIT(2) diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index c41f2252945e..c655c9de1dde 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -340,7 +340,7 @@ static int tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command /* CMD12 is handled by hardware */ if (cmd->opcode == MMC_STOP_TRANSMISSION && !cmd->arg) { - sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x001); + sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, TMIO_STOP_STP); return 0; } @@ -367,7 +367,7 @@ static int tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command if (data) { c |= DATA_PRESENT; if (data->blocks > 1) { - sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x100); + sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, TMIO_STOP_SEC); c |= TRANSFER_MULTI; /* @@ -554,7 +554,7 @@ void tmio_mmc_do_data_irq(struct tmio_mmc_host *host) if (stop) { if (stop->opcode == MMC_STOP_TRANSMISSION && !stop->arg) - sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x000); + sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0); else BUG(); } From d8acd16c84c32a65a291e8f16ba4cb3b6d182e30 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 14 Mar 2017 11:09:17 +0100 Subject: [PATCH 021/148] mmc: host: tmio: fix minor typos in comments Making sure we match the actual register names. Reviewed-by: Simon Horman Signed-off-by: Wolfram Sang Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h index a484ed87bcd0..d0edb5730d3f 100644 --- a/drivers/mmc/host/tmio_mmc.h +++ b/drivers/mmc/host/tmio_mmc.h @@ -54,7 +54,7 @@ #define TMIO_STOP_STP BIT(0) #define TMIO_STOP_SEC BIT(8) -/* Definitions for values the CTRL_STATUS register can take. */ +/* Definitions for values the CTL_STATUS register can take */ #define TMIO_STAT_CMDRESPEND BIT(0) #define TMIO_STAT_DATAEND BIT(2) #define TMIO_STAT_CARD_REMOVE BIT(3) @@ -65,7 +65,7 @@ #define TMIO_STAT_CARD_INSERT_A BIT(9) #define TMIO_STAT_SIGSTATE_A BIT(10) -/* These belong technically to CTRL_STATUS2, but the driver merges them */ +/* These belong technically to CTL_STATUS2, but the driver merges them */ #define TMIO_STAT_CMD_IDX_ERR BIT(16) #define TMIO_STAT_CRCFAIL BIT(17) #define TMIO_STAT_STOPBIT_ERR BIT(18) @@ -89,7 +89,7 @@ #define TMIO_BBS 512 /* Boot block size */ -/* Definitions for values the CTRL_SDIO_STATUS register can take. */ +/* Definitions for values the CTL_SDIO_STATUS register can take */ #define TMIO_SDIO_STAT_IOIRQ 0x0001 #define TMIO_SDIO_STAT_EXPUB52 0x4000 #define TMIO_SDIO_STAT_EXWT 0x8000 From 022f731e03d190d0e4f52f2b1bd90fae0eadd56d Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 14 Mar 2017 11:09:18 +0100 Subject: [PATCH 022/148] mmc: host: tmio: don't BUG on unsupported stop commands Halting the kernel on an unsupported stop command seems overkill, report the error and say what we already did (due to autocmd12) instead. Reviewed-by: Simon Horman Signed-off-by: Wolfram Sang Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc_pio.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index c655c9de1dde..42a912a3de67 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -553,10 +553,11 @@ void tmio_mmc_do_data_irq(struct tmio_mmc_host *host) } if (stop) { - if (stop->opcode == MMC_STOP_TRANSMISSION && !stop->arg) - sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0); - else - BUG(); + if (stop->opcode != MMC_STOP_TRANSMISSION || stop->arg) + dev_err(&host->pdev->dev, "unsupported stop: CMD%u,0x%x. We did CMD12,0\n", + stop->opcode, stop->arg); + + sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0); } schedule_work(&host->done); From eb7c00e1461124ad0e85f1a9a3954d7164809c13 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 14 Mar 2017 11:09:19 +0100 Subject: [PATCH 023/148] mmc: host: tmio: fill in response from auto cmd12 After we received the dataend interrupt, R1 response register carries the value from the automatically generated stop command. Report that info back to the MMC block layer, so we will be notified in case of e.g. ECC errors which happened during the last transfer. Reviewed-by: Simon Horman Tested-by: Yoshihiro Shimoda Signed-off-by: Wolfram Sang Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc_pio.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index 42a912a3de67..5b01d22932cd 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -557,6 +557,9 @@ void tmio_mmc_do_data_irq(struct tmio_mmc_host *host) dev_err(&host->pdev->dev, "unsupported stop: CMD%u,0x%x. We did CMD12,0\n", stop->opcode, stop->arg); + /* fill in response from auto CMD12 */ + stop->resp[0] = sd_ctrl_read16_and_16_as_32(host, CTL_RESPONSE); + sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0); } From a01fc2a295af6418d665d8f9c18af6017a929065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zegan?= Date: Sat, 18 Feb 2017 18:06:47 +0100 Subject: [PATCH 024/148] mmc: meson-gx: remove mmc host on device removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mmc host was added in meson_mmc_probe, but never removed in meson_mmc_remove. Fix that by removing the host before deallocating other resources. Signed-off-by: Michał Zegan Tested-by: Michał Zegan Acked-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 62597638ccaf..c574e1519623 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -793,6 +793,8 @@ static int meson_mmc_remove(struct platform_device *pdev) { struct meson_host *host = dev_get_drvdata(&pdev->dev); + mmc_remove_host(host->mmc); + /* disable interrupts */ writel(0, host->regs + SD_EMMC_IRQ_EN); From 20567be9d2e64c3f8302ae66d9ca69899d1c5e8a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 8 Mar 2017 20:00:39 +0100 Subject: [PATCH 025/148] mmc: tegra: Support module reset The device tree binding for the SDHCI controller found on Tegra SoCs specifies that a reset control can be provided by the device tree. No code was ever added to support the module reset, which can cause the driver to try and access registers from a module that's in reset. On most Tegra SoC generations doing so would cause a hang. Note that it's unlikely to see this happen because on most platforms these resets will have been deasserted by the bootloader. However the portability can be improved by making sure the driver deasserts the reset before accessing any registers. Since resets are synchronous on Tegra SoCs, the platform driver needs to implement a custom ->remove() callback now to make sure the clock is disabled after the reset is asserted. Acked-by: Adrian Hunter Signed-off-by: Thierry Reding Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-tegra.c | 43 +++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 20b6ff5b4af1..9d31ee8988ef 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,8 @@ struct sdhci_tegra { struct gpio_desc *power_gpio; bool ddr_signaling; bool pad_calib_required; + + struct reset_control *rst; }; static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) @@ -489,6 +492,25 @@ static int sdhci_tegra_probe(struct platform_device *pdev) clk_prepare_enable(clk); pltfm_host->clk = clk; + tegra_host->rst = devm_reset_control_get(&pdev->dev, "sdhci"); + if (IS_ERR(tegra_host->rst)) { + rc = PTR_ERR(tegra_host->rst); + dev_err(&pdev->dev, "failed to get reset control: %d\n", rc); + goto err_rst_get; + } + + rc = reset_control_assert(tegra_host->rst); + if (rc) + goto err_rst_get; + + usleep_range(2000, 4000); + + rc = reset_control_deassert(tegra_host->rst); + if (rc) + goto err_rst_get; + + usleep_range(2000, 4000); + rc = sdhci_add_host(host); if (rc) goto err_add_host; @@ -496,6 +518,8 @@ static int sdhci_tegra_probe(struct platform_device *pdev) return 0; err_add_host: + reset_control_assert(tegra_host->rst); +err_rst_get: clk_disable_unprepare(pltfm_host->clk); err_clk_get: err_power_req: @@ -504,6 +528,23 @@ err_parse_dt: return rc; } +static int sdhci_tegra_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host); + + sdhci_remove_host(host, 0); + + reset_control_assert(tegra_host->rst); + usleep_range(2000, 4000); + clk_disable_unprepare(pltfm_host->clk); + + sdhci_pltfm_free(pdev); + + return 0; +} + static struct platform_driver sdhci_tegra_driver = { .driver = { .name = "sdhci-tegra", @@ -511,7 +552,7 @@ static struct platform_driver sdhci_tegra_driver = { .pm = &sdhci_pltfm_pmops, }, .probe = sdhci_tegra_probe, - .remove = sdhci_pltfm_unregister, + .remove = sdhci_tegra_remove, }; module_platform_driver(sdhci_tegra_driver); From 4346b7c7941d4f98214af01d04850c91a706322e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 8 Mar 2017 20:00:40 +0100 Subject: [PATCH 026/148] mmc: tegra: Add Tegra186 support The SDHCI controller found on NVIDIA Tegra186 SoCs is very similar to the one on prior generations of Tegra and can be supported by the same driver. Acked-by: Adrian Hunter Signed-off-by: Thierry Reding Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-tegra.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 9d31ee8988ef..7f93079c7a3a 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -434,7 +434,23 @@ static const struct sdhci_tegra_soc_data soc_data_tegra210 = { .pdata = &sdhci_tegra210_pdata, }; +static const struct sdhci_pltfm_data sdhci_tegra186_pdata = { + .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | + SDHCI_QUIRK_SINGLE_POWER_WRITE | + SDHCI_QUIRK_NO_HISPD_BIT | + SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + .ops = &tegra114_sdhci_ops, +}; + +static const struct sdhci_tegra_soc_data soc_data_tegra186 = { + .pdata = &sdhci_tegra186_pdata, +}; + static const struct of_device_id sdhci_tegra_dt_match[] = { + { .compatible = "nvidia,tegra186-sdhci", .data = &soc_data_tegra186 }, { .compatible = "nvidia,tegra210-sdhci", .data = &soc_data_tegra210 }, { .compatible = "nvidia,tegra124-sdhci", .data = &soc_data_tegra124 }, { .compatible = "nvidia,tegra114-sdhci", .data = &soc_data_tegra114 }, From d12990f9900f49e7f834b0d2cf6ccebcf4cb698f Mon Sep 17 00:00:00 2001 From: Piotr Sroka Date: Mon, 6 Mar 2017 08:28:41 +0000 Subject: [PATCH 027/148] mmc: sdhci-cadence: add HS400 enhanced strobe support Add support for HS400ES mode to Cadence SDHCI driver. Signed-off-by: Piotr Sroka Reviewed-by: Masahiro Yamada Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 57 ++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 316cfec3f005..48f641953844 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -40,6 +40,7 @@ #define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 #define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 #define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 +#define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 /* SRS - Slot Register Set (SDHCI-compatible) */ #define SDHCI_CDNS_SRS_BASE 0x200 @@ -64,6 +65,7 @@ struct sdhci_cdns_priv { void __iomem *hrs_addr; + bool enhanced_strobe; }; static void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, @@ -108,11 +110,30 @@ static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) return host->max_clk / 1000; } +static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode) +{ + u32 tmp; + + /* The speed mode for eMMC is selected by HRS06 register */ + tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); + tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK; + tmp |= mode; + writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); +} + +static u32 sdhci_cdns_get_emmc_mode(struct sdhci_cdns_priv *priv) +{ + u32 tmp; + + tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); + return tmp & SDHCI_CDNS_HRS06_MODE_MASK; +} + static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, unsigned int timing) { struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); - u32 mode, tmp; + u32 mode; switch (timing) { case MMC_TIMING_MMC_HS: @@ -125,18 +146,17 @@ static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; break; case MMC_TIMING_MMC_HS400: - mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; + if (priv->enhanced_strobe) + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES; + else + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; break; default: mode = SDHCI_CDNS_HRS06_MODE_SD; break; } - /* The speed mode for eMMC is selected by HRS06 register */ - tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); - tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK; - tmp |= mode; - writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); + sdhci_cdns_set_emmc_mode(priv, mode); /* For SD, fall back to the default handler */ if (mode == SDHCI_CDNS_HRS06_MODE_SD) @@ -213,6 +233,26 @@ static int sdhci_cdns_execute_tuning(struct mmc_host *mmc, u32 opcode) return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); } +static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + u32 mode; + + priv->enhanced_strobe = ios->enhanced_strobe; + + mode = sdhci_cdns_get_emmc_mode(priv); + + if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400 && ios->enhanced_strobe) + sdhci_cdns_set_emmc_mode(priv, + SDHCI_CDNS_HRS06_MODE_MMC_HS400ES); + + if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400ES && !ios->enhanced_strobe) + sdhci_cdns_set_emmc_mode(priv, + SDHCI_CDNS_HRS06_MODE_MMC_HS400); +} + static int sdhci_cdns_probe(struct platform_device *pdev) { struct sdhci_host *host; @@ -240,8 +280,11 @@ static int sdhci_cdns_probe(struct platform_device *pdev) priv = sdhci_cdns_priv(host); priv->hrs_addr = host->ioaddr; + priv->enhanced_strobe = false; host->ioaddr += SDHCI_CDNS_SRS_BASE; host->mmc_host_ops.execute_tuning = sdhci_cdns_execute_tuning; + host->mmc_host_ops.hs400_enhanced_strobe = + sdhci_cdns_hs400_enhanced_strobe; ret = mmc_of_parse(host->mmc); if (ret) From d36f47c54b297f22b766d31422b2044b08473924 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 10 Mar 2017 11:17:31 +0800 Subject: [PATCH 028/148] mmc: sdhci-pxav3: remove unnecessary platform_set_drvdata() call sdhci_pltfm_init() has already set the platform drvdata for us, remove the unnecessary platform_set_drvdata() call. Signed-off-by: Jisheng Zhang Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pxav3.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index d0f5c05fbc19..132670a612a0 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -480,8 +480,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) goto err_add_host; } - platform_set_drvdata(pdev, host); - if (host->mmc->pm_caps & MMC_PM_WAKE_SDIO_IRQ) device_init_wakeup(&pdev->dev, 1); From 0092faf5a871db6b6d730bf54c0ad28575edfb6a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 10 Mar 2017 11:17:32 +0800 Subject: [PATCH 029/148] mmc: sdhci-pxav2: remove unnecessary platform_set_drvdata() call sdhci_pltfm_init() has already set the platform drvdata for us, remove the unnecessary platform_set_drvdata() call. Signed-off-by: Jisheng Zhang Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pxav2.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c index 80513f54e9d4..995083ce1c46 100644 --- a/drivers/mmc/host/sdhci-pxav2.c +++ b/drivers/mmc/host/sdhci-pxav2.c @@ -226,8 +226,6 @@ static int sdhci_pxav2_probe(struct platform_device *pdev) goto err_add_host; } - platform_set_drvdata(pdev, host); - return 0; err_add_host: From 9449659d66e2ce03069e89212f5000925058a533 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 10 Mar 2017 11:17:33 +0800 Subject: [PATCH 030/148] mmc: sdhci-st: remove unnecessary platform_set_drvdata() call sdhci_pltfm_init() has already set the platform drvdata for us, remove the unnecessary platform_set_drvdata() call. Signed-off-by: Jisheng Zhang Acked-by: Patrice Chotard Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-st.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-st.c b/drivers/mmc/host/sdhci-st.c index ed92ce729dde..7fff6490855c 100644 --- a/drivers/mmc/host/sdhci-st.c +++ b/drivers/mmc/host/sdhci-st.c @@ -418,8 +418,6 @@ static int sdhci_st_probe(struct platform_device *pdev) goto err_out; } - platform_set_drvdata(pdev, host); - host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_info(&pdev->dev, "SDHCI ST Initialised: Host Version: 0x%x Vendor Version 0x%x\n", From 4166b3036e930fab7328688d26770a20752e76f0 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 14 Mar 2017 20:01:51 +0100 Subject: [PATCH 031/148] dt-bindings: mmc: tegra: Reformat compatible string list The list of compatible strings is somewhat difficult to read and extend. Reformat it into a list to make it more easily extensible. While at it, also remove the "plus one of the above" clause because it isn't actually valid. Signed-off-by: Thierry Reding Signed-off-by: Ulf Hansson --- .../devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt b/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt index 15b8368ee1f2..c15aa22d4570 100644 --- a/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt +++ b/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt @@ -7,11 +7,12 @@ This file documents differences between the core properties described by mmc.txt and the properties used by the sdhci-tegra driver. Required properties: -- compatible : For Tegra20, must contain "nvidia,tegra20-sdhci". - For Tegra30, must contain "nvidia,tegra30-sdhci". For Tegra114, - must contain "nvidia,tegra114-sdhci". For Tegra124, must contain - "nvidia,tegra124-sdhci". Otherwise, must contain "nvidia,-sdhci", - plus one of the above, where is tegra132 or tegra210. +- compatible : should be one of: + - "nvidia,tegra20-sdhci": for Tegra20 + - "nvidia,tegra30-sdhci": for Tegra30 + - "nvidia,tegra114-sdhci": for Tegra114 + - "nvidia,tegra124-sdhci": for Tegra124 and Tegra132 + - "nvidia,tegra210-sdhci": for Tegra210 - clocks : Must contain one entry, for the module clock. See ../clocks/clock-bindings.txt for details. - resets : Must contain an entry for each entry in reset-names. From e8ce6742f809bcb1bbc8d9ce27d72ee1de7a551a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 14 Mar 2017 20:01:52 +0100 Subject: [PATCH 032/148] dt-bindings: mmc: tegra: Document Tegra186 compatible string The SDHCI controller found on Tegra186 in very similar to the controller found on earlier generations of Tegra. Signed-off-by: Thierry Reding Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt b/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt index c15aa22d4570..9bce57862ed6 100644 --- a/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt +++ b/Documentation/devicetree/bindings/mmc/nvidia,tegra20-sdhci.txt @@ -13,6 +13,7 @@ Required properties: - "nvidia,tegra114-sdhci": for Tegra114 - "nvidia,tegra124-sdhci": for Tegra124 and Tegra132 - "nvidia,tegra210-sdhci": for Tegra210 + - "nvidia,tegra186-sdhci": for Tegra186 - clocks : Must contain one entry, for the module clock. See ../clocks/clock-bindings.txt for details. - resets : Must contain an entry for each entry in reset-names. From ce473d5b49d6ef76d35e6d00daa8c5a8fed85943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zegan?= Date: Tue, 14 Mar 2017 21:05:20 +0100 Subject: [PATCH 033/148] mmc: meson-gx: fix error path when driver initialization fails before enabling clocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes the case where meson_mmc_probe function fails before core_clk is enabled. Originally, that would result in a try to disable the core clock, and if it was not already enabled, it would result in a kernel warning. This issue is similar to the one with div_clk. Fix it by introducing another error path used only between successfully enabling the core clock, and successfully enabling the div clock. That would ensure that core clock is disabled only if it was enabled before. Signed-off-by: Michał Zegan Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index c574e1519623..bd075fef08d4 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -743,7 +743,7 @@ static int meson_mmc_probe(struct platform_device *pdev) ret = meson_mmc_clk_init(host); if (ret) - goto free_host; + goto err_core_clk; /* Stop execution */ writel(0, host->regs + SD_EMMC_START); @@ -783,8 +783,9 @@ static int meson_mmc_probe(struct platform_device *pdev) err_div_clk: clk_disable_unprepare(host->cfg_div_clk); -free_host: +err_core_clk: clk_disable_unprepare(host->core_clk); +free_host: mmc_free_host(mmc); return ret; } From 9b980d950e5bcdce6dbc15a9c00ff960beeea28b Mon Sep 17 00:00:00 2001 From: tcharding Date: Wed, 15 Mar 2017 19:48:03 +1100 Subject: [PATCH 034/148] mmc: core: guard dereference of optional parameter Various functions take as parameter an optional pointer. Pointer should be guarded with non-NULL check before dereferencing. Add non-NULL check before dereference of pointer. Signed-off-by: Tobin C. Harding Signed-off-by: Ulf Hansson --- drivers/mmc/core/sdio_io.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index 74195d772f5a..348231418565 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -373,7 +373,8 @@ u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret) u8 val; if (!func) { - *err_ret = -EINVAL; + if (err_ret) + *err_ret = -EINVAL; return 0xFF; } @@ -407,7 +408,8 @@ void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret) int ret; if (!func) { - *err_ret = -EINVAL; + if (err_ret) + *err_ret = -EINVAL; return; } @@ -635,7 +637,8 @@ unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr, unsigned char val; if (!func) { - *err_ret = -EINVAL; + if (err_ret) + *err_ret = -EINVAL; return 0xFF; } @@ -673,7 +676,8 @@ void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, int ret; if (!func) { - *err_ret = -EINVAL; + if (err_ret) + *err_ret = -EINVAL; return; } From 8e11b284eaa86863f700216e4d3c87e7d7b3f44c Mon Sep 17 00:00:00 2001 From: tcharding Date: Wed, 15 Mar 2017 19:48:04 +1100 Subject: [PATCH 035/148] mmc: core: simplify return code File contains multiple functions doing variations on the same thing, sdio_readb(), sdio_writeb()f, sdio_readw(), sdio_writew() etc. Although the functions have very similar logic the code is laid out in a variety of ways. This makes it overly complicated to read. There is a already a nice clean chunk of code, if we use this format for all instances then we will have cleaned up the code, reduced the line count and lessened the cognitive load required while reading. Less lines equals less bugs. Pick the most simple and clear code flow and change all functions to be the same. Signed-off-by: Tobin C. Harding Signed-off-by: Ulf Hansson --- drivers/mmc/core/sdio_io.c | 42 ++++++++++++-------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index 348231418565..d40744bbafa9 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -378,15 +378,11 @@ u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret) return 0xFF; } - if (err_ret) - *err_ret = 0; - ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val); - if (ret) { - if (err_ret) - *err_ret = ret; + if (err_ret) + *err_ret = ret; + if (ret) return 0xFF; - } return val; } @@ -443,7 +439,7 @@ u8 sdio_writeb_readb(struct sdio_func *func, u8 write_byte, if (err_ret) *err_ret = ret; if (ret) - val = 0xff; + return 0xff; return val; } @@ -531,15 +527,11 @@ u16 sdio_readw(struct sdio_func *func, unsigned int addr, int *err_ret) { int ret; - if (err_ret) - *err_ret = 0; - ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2); - if (ret) { - if (err_ret) - *err_ret = ret; + if (err_ret) + *err_ret = ret; + if (ret) return 0xFFFF; - } return le16_to_cpup((__le16 *)func->tmpbuf); } @@ -583,15 +575,11 @@ u32 sdio_readl(struct sdio_func *func, unsigned int addr, int *err_ret) { int ret; - if (err_ret) - *err_ret = 0; - ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4); - if (ret) { - if (err_ret) - *err_ret = ret; + if (err_ret) + *err_ret = ret; + if (ret) return 0xFFFFFFFF; - } return le32_to_cpup((__le32 *)func->tmpbuf); } @@ -642,15 +630,11 @@ unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr, return 0xFF; } - if (err_ret) - *err_ret = 0; - ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val); - if (ret) { - if (err_ret) - *err_ret = ret; + if (err_ret) + *err_ret = ret; + if (ret) return 0xFF; - } return val; } From ebdb4d8e0d2577bd8e1d0d54e1c2e9445bdc90e6 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 8 Mar 2017 10:19:01 +0100 Subject: [PATCH 036/148] dt-bindings: Add binding for brcm,bcm2835-sdhost. This is the other SD controller on the platform, which can be swapped to the role of SD card host using pin muxing. Signed-off-by: Eric Anholt Signed-off-by: Gerd Hoffmann Acked-by: Rob Herring Signed-off-by: Ulf Hansson --- .../bindings/mmc/brcm,bcm2835-sdhost.txt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhost.txt diff --git a/Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhost.txt b/Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhost.txt new file mode 100644 index 000000000000..d876580ae3b8 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/brcm,bcm2835-sdhost.txt @@ -0,0 +1,23 @@ +Broadcom BCM2835 SDHOST controller + +This file documents differences between the core properties described +by mmc.txt and the properties that represent the BCM2835 controller. + +Required properties: +- compatible: Should be "brcm,bcm2835-sdhost". +- clocks: The clock feeding the SDHOST controller. + +Optional properties: +- dmas: DMA channel for read and write. + See Documentation/devicetree/bindings/dma/dma.txt for details + +Example: + +sdhost: mmc@7e202000 { + compatible = "brcm,bcm2835-sdhost"; + reg = <0x7e202000 0x100>; + interrupts = <2 24>; + clocks = <&clocks BCM2835_CLOCK_VPU>; + dmas = <&dma 13>; + dma-names = "rx-tx"; +}; From 660fc733bd7436f4fa1a351376493e635514ed64 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 8 Mar 2017 10:19:03 +0100 Subject: [PATCH 037/148] mmc: bcm2835: Add new driver for the sdhost controller. The 2835 has two SD controllers: The Arasan sdhci controller (supported by the iproc driver) and a custom sdhost controller. This patch adds a driver for the latter. The sdhci controller supports both sdcard and sdio. The sdhost controller supports the sdcard only, but has better performance. Also note that the rpi3 has sdio wifi, so driving the sdcard with the sdhost controller allows to use the sdhci controller for wifi support. The configuration is done by devicetree via pin muxing. Both SD controller are available on the same pins (2 pin groups = pin 22 to 27 + pin 48 to 53). So it's possible to use both SD controllers at the same time with different pin groups. The code was originally written by Phil Elwell in the downstream Rasbperry Pi tree. In preparation for the upstream merge it was cleaned up and the code base was moderized by Eric Anholt, Stefan Wahren and Gerd Hoffmann. Signed-off-by: Eric Anholt Signed-off-by: Stefan Wahren Signed-off-by: Gerd Hoffmann Signed-off-by: Ulf Hansson --- drivers/mmc/host/Kconfig | 14 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/bcm2835.c | 1465 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1480 insertions(+) create mode 100644 drivers/mmc/host/bcm2835.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f08691a58d7e..a638cd0d80be 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -799,6 +799,20 @@ config MMC_TOSHIBA_PCI depends on PCI help +config MMC_BCM2835 + tristate "Broadcom BCM2835 SDHOST MMC Controller support" + depends on ARCH_BCM2835 || COMPILE_TEST + depends on HAS_DMA + help + This selects the BCM2835 SDHOST MMC controller. If you have + a BCM2835 platform with SD or MMC devices, say Y or M here. + + Note that the BCM2835 has two SD controllers: The Arasan + sdhci controller (supported by MMC_SDHCI_IPROC) and a custom + sdhost controller (supported by this driver). + + If unsure, say N. + config MMC_MTK tristate "MediaTek SD/MMC Card Interface support" depends on HAS_DMA diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 6d548c4ee2fa..bc2c2e2c68c0 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o +obj-$(CONFIG_MMC_BCM2835) += bcm2835.o obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c new file mode 100644 index 000000000000..7d1b0db7fbb7 --- /dev/null +++ b/drivers/mmc/host/bcm2835.c @@ -0,0 +1,1465 @@ +/* + * bcm2835 sdhost driver. + * + * The 2835 has two SD controllers: The Arasan sdhci controller + * (supported by the iproc driver) and a custom sdhost controller + * (supported by this driver). + * + * The sdhci controller supports both sdcard and sdio. The sdhost + * controller supports the sdcard only, but has better performance. + * Also note that the rpi3 has sdio wifi, so driving the sdcard with + * the sdhost controller allows to use the sdhci controller for wifi + * support. + * + * The configuration is done by devicetree via pin muxing. Both + * SD controller are available on the same pins (2 pin groups = pin 22 + * to 27 + pin 48 to 53). So it's possible to use both SD controllers + * at the same time with different pin groups. + * + * Author: Phil Elwell + * Copyright (C) 2015-2016 Raspberry Pi (Trading) Ltd. + * + * Based on + * mmc-bcm2835.c by Gellert Weisz + * which is, in turn, based on + * sdhci-bcm2708.c by Broadcom + * sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko + * sdhci.c and sdhci-pci.c by Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SDCMD 0x00 /* Command to SD card - 16 R/W */ +#define SDARG 0x04 /* Argument to SD card - 32 R/W */ +#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ +#define SDCDIV 0x0c /* Start value for clock divider - 11 R/W */ +#define SDRSP0 0x10 /* SD card response (31:0) - 32 R */ +#define SDRSP1 0x14 /* SD card response (63:32) - 32 R */ +#define SDRSP2 0x18 /* SD card response (95:64) - 32 R */ +#define SDRSP3 0x1c /* SD card response (127:96) - 32 R */ +#define SDHSTS 0x20 /* SD host status - 11 R/W */ +#define SDVDD 0x30 /* SD card power control - 1 R/W */ +#define SDEDM 0x34 /* Emergency Debug Mode - 13 R/W */ +#define SDHCFG 0x38 /* Host configuration - 2 R/W */ +#define SDHBCT 0x3c /* Host byte count (debug) - 32 R/W */ +#define SDDATA 0x40 /* Data to/from SD card - 32 R/W */ +#define SDHBLC 0x50 /* Host block count (SDIO/SDHC) - 9 R/W */ + +#define SDCMD_NEW_FLAG 0x8000 +#define SDCMD_FAIL_FLAG 0x4000 +#define SDCMD_BUSYWAIT 0x800 +#define SDCMD_NO_RESPONSE 0x400 +#define SDCMD_LONG_RESPONSE 0x200 +#define SDCMD_WRITE_CMD 0x80 +#define SDCMD_READ_CMD 0x40 +#define SDCMD_CMD_MASK 0x3f + +#define SDCDIV_MAX_CDIV 0x7ff + +#define SDHSTS_BUSY_IRPT 0x400 +#define SDHSTS_BLOCK_IRPT 0x200 +#define SDHSTS_SDIO_IRPT 0x100 +#define SDHSTS_REW_TIME_OUT 0x80 +#define SDHSTS_CMD_TIME_OUT 0x40 +#define SDHSTS_CRC16_ERROR 0x20 +#define SDHSTS_CRC7_ERROR 0x10 +#define SDHSTS_FIFO_ERROR 0x08 +/* Reserved */ +/* Reserved */ +#define SDHSTS_DATA_FLAG 0x01 + +#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC7_ERROR | \ + SDHSTS_CRC16_ERROR | \ + SDHSTS_REW_TIME_OUT | \ + SDHSTS_FIFO_ERROR) + +#define SDHSTS_ERROR_MASK (SDHSTS_CMD_TIME_OUT | \ + SDHSTS_TRANSFER_ERROR_MASK) + +#define SDHCFG_BUSY_IRPT_EN BIT(10) +#define SDHCFG_BLOCK_IRPT_EN BIT(8) +#define SDHCFG_SDIO_IRPT_EN BIT(5) +#define SDHCFG_DATA_IRPT_EN BIT(4) +#define SDHCFG_SLOW_CARD BIT(3) +#define SDHCFG_WIDE_EXT_BUS BIT(2) +#define SDHCFG_WIDE_INT_BUS BIT(1) +#define SDHCFG_REL_CMD_LINE BIT(0) + +#define SDVDD_POWER_OFF 0 +#define SDVDD_POWER_ON 1 + +#define SDEDM_FORCE_DATA_MODE BIT(19) +#define SDEDM_CLOCK_PULSE BIT(20) +#define SDEDM_BYPASS BIT(21) + +#define SDEDM_WRITE_THRESHOLD_SHIFT 9 +#define SDEDM_READ_THRESHOLD_SHIFT 14 +#define SDEDM_THRESHOLD_MASK 0x1f + +#define SDEDM_FSM_MASK 0xf +#define SDEDM_FSM_IDENTMODE 0x0 +#define SDEDM_FSM_DATAMODE 0x1 +#define SDEDM_FSM_READDATA 0x2 +#define SDEDM_FSM_WRITEDATA 0x3 +#define SDEDM_FSM_READWAIT 0x4 +#define SDEDM_FSM_READCRC 0x5 +#define SDEDM_FSM_WRITECRC 0x6 +#define SDEDM_FSM_WRITEWAIT1 0x7 +#define SDEDM_FSM_POWERDOWN 0x8 +#define SDEDM_FSM_POWERUP 0x9 +#define SDEDM_FSM_WRITESTART1 0xa +#define SDEDM_FSM_WRITESTART2 0xb +#define SDEDM_FSM_GENPULSES 0xc +#define SDEDM_FSM_WRITEWAIT2 0xd +#define SDEDM_FSM_STARTPOWDOWN 0xf + +#define SDDATA_FIFO_WORDS 16 + +#define FIFO_READ_THRESHOLD 4 +#define FIFO_WRITE_THRESHOLD 4 +#define SDDATA_FIFO_PIO_BURST 8 + +#define PIO_THRESHOLD 1 /* Maximum block count for PIO (0 = always DMA) */ + +struct bcm2835_host { + spinlock_t lock; + struct mutex mutex; + + void __iomem *ioaddr; + u32 phys_addr; + + struct mmc_host *mmc; + struct platform_device *pdev; + + int clock; /* Current clock speed */ + unsigned int max_clk; /* Max possible freq */ + struct work_struct dma_work; + struct delayed_work timeout_work; /* Timer for timeouts */ + struct sg_mapping_iter sg_miter; /* SG state for PIO */ + unsigned int blocks; /* remaining PIO blocks */ + int irq; /* Device IRQ */ + + u32 ns_per_fifo_word; + + /* cached registers */ + u32 hcfg; + u32 cdiv; + + struct mmc_request *mrq; /* Current request */ + struct mmc_command *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + bool data_complete:1;/* Data finished before cmd */ + bool use_busy:1; /* Wait for busy interrupt */ + bool use_sbc:1; /* Send CMD23 */ + + /* for threaded irq handler */ + bool irq_block; + bool irq_busy; + bool irq_data; + + /* DMA part */ + struct dma_chan *dma_chan_rxtx; + struct dma_chan *dma_chan; + struct dma_slave_config dma_cfg_rx; + struct dma_slave_config dma_cfg_tx; + struct dma_async_tx_descriptor *dma_desc; + u32 dma_dir; + u32 drain_words; + struct page *drain_page; + u32 drain_offset; + bool use_dma; +}; + +static void bcm2835_dumpcmd(struct bcm2835_host *host, struct mmc_command *cmd, + const char *label) +{ + struct device *dev = &host->pdev->dev; + + if (!cmd) + return; + + dev_dbg(dev, "%c%s op %d arg 0x%x flags 0x%x - resp %08x %08x %08x %08x, err %d\n", + (cmd == host->cmd) ? '>' : ' ', + label, cmd->opcode, cmd->arg, cmd->flags, + cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], + cmd->error); +} + +static void bcm2835_dumpregs(struct bcm2835_host *host) +{ + struct mmc_request *mrq = host->mrq; + struct device *dev = &host->pdev->dev; + + if (mrq) { + bcm2835_dumpcmd(host, mrq->sbc, "sbc"); + bcm2835_dumpcmd(host, mrq->cmd, "cmd"); + if (mrq->data) { + dev_dbg(dev, "data blocks %x blksz %x - err %d\n", + mrq->data->blocks, + mrq->data->blksz, + mrq->data->error); + } + bcm2835_dumpcmd(host, mrq->stop, "stop"); + } + + dev_dbg(dev, "=========== REGISTER DUMP ===========\n"); + dev_dbg(dev, "SDCMD 0x%08x\n", readl(host->ioaddr + SDCMD)); + dev_dbg(dev, "SDARG 0x%08x\n", readl(host->ioaddr + SDARG)); + dev_dbg(dev, "SDTOUT 0x%08x\n", readl(host->ioaddr + SDTOUT)); + dev_dbg(dev, "SDCDIV 0x%08x\n", readl(host->ioaddr + SDCDIV)); + dev_dbg(dev, "SDRSP0 0x%08x\n", readl(host->ioaddr + SDRSP0)); + dev_dbg(dev, "SDRSP1 0x%08x\n", readl(host->ioaddr + SDRSP1)); + dev_dbg(dev, "SDRSP2 0x%08x\n", readl(host->ioaddr + SDRSP2)); + dev_dbg(dev, "SDRSP3 0x%08x\n", readl(host->ioaddr + SDRSP3)); + dev_dbg(dev, "SDHSTS 0x%08x\n", readl(host->ioaddr + SDHSTS)); + dev_dbg(dev, "SDVDD 0x%08x\n", readl(host->ioaddr + SDVDD)); + dev_dbg(dev, "SDEDM 0x%08x\n", readl(host->ioaddr + SDEDM)); + dev_dbg(dev, "SDHCFG 0x%08x\n", readl(host->ioaddr + SDHCFG)); + dev_dbg(dev, "SDHBCT 0x%08x\n", readl(host->ioaddr + SDHBCT)); + dev_dbg(dev, "SDHBLC 0x%08x\n", readl(host->ioaddr + SDHBLC)); + dev_dbg(dev, "===========================================\n"); +} + +static void bcm2835_reset_internal(struct bcm2835_host *host) +{ + u32 temp; + + writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD); + writel(0, host->ioaddr + SDCMD); + writel(0, host->ioaddr + SDARG); + writel(0xf00000, host->ioaddr + SDTOUT); + writel(0, host->ioaddr + SDCDIV); + writel(0x7f8, host->ioaddr + SDHSTS); /* Write 1s to clear */ + writel(0, host->ioaddr + SDHCFG); + writel(0, host->ioaddr + SDHBCT); + writel(0, host->ioaddr + SDHBLC); + + /* Limit fifo usage due to silicon bug */ + temp = readl(host->ioaddr + SDEDM); + temp &= ~((SDEDM_THRESHOLD_MASK << SDEDM_READ_THRESHOLD_SHIFT) | + (SDEDM_THRESHOLD_MASK << SDEDM_WRITE_THRESHOLD_SHIFT)); + temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) | + (FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT); + writel(temp, host->ioaddr + SDEDM); + msleep(20); + writel(SDVDD_POWER_ON, host->ioaddr + SDVDD); + msleep(20); + host->clock = 0; + writel(host->hcfg, host->ioaddr + SDHCFG); + writel(host->cdiv, host->ioaddr + SDCDIV); +} + +static void bcm2835_reset(struct mmc_host *mmc) +{ + struct bcm2835_host *host = mmc_priv(mmc); + + if (host->dma_chan) + dmaengine_terminate_sync(host->dma_chan); + bcm2835_reset_internal(host); +} + +static void bcm2835_finish_command(struct bcm2835_host *host); + +static void bcm2835_wait_transfer_complete(struct bcm2835_host *host) +{ + int timediff; + u32 alternate_idle; + + alternate_idle = (host->mrq->data->flags & MMC_DATA_READ) ? + SDEDM_FSM_READWAIT : SDEDM_FSM_WRITESTART1; + + timediff = 0; + + while (1) { + u32 edm, fsm; + + edm = readl(host->ioaddr + SDEDM); + fsm = edm & SDEDM_FSM_MASK; + + if ((fsm == SDEDM_FSM_IDENTMODE) || + (fsm == SDEDM_FSM_DATAMODE)) + break; + if (fsm == alternate_idle) { + writel(edm | SDEDM_FORCE_DATA_MODE, + host->ioaddr + SDEDM); + break; + } + + timediff++; + if (timediff == 100000) { + dev_err(&host->pdev->dev, + "wait_transfer_complete - still waiting after %d retries\n", + timediff); + bcm2835_dumpregs(host); + host->mrq->data->error = -ETIMEDOUT; + return; + } + cpu_relax(); + } +} + +static void bcm2835_dma_complete(void *param) +{ + struct bcm2835_host *host = param; + + schedule_work(&host->dma_work); +} + +static void bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read) +{ + unsigned long flags; + size_t blksize; + unsigned long wait_max; + + blksize = host->data->blksz; + + wait_max = jiffies + msecs_to_jiffies(500); + + local_irq_save(flags); + + while (blksize) { + int copy_words; + u32 hsts = 0; + size_t len; + u32 *buf; + + if (!sg_miter_next(&host->sg_miter)) { + host->data->error = -EINVAL; + break; + } + + len = min(host->sg_miter.length, blksize); + if (len % 4) { + host->data->error = -EINVAL; + break; + } + + blksize -= len; + host->sg_miter.consumed = len; + + buf = (u32 *)host->sg_miter.addr; + + copy_words = len / 4; + + while (copy_words) { + int burst_words, words; + u32 edm; + + burst_words = min(SDDATA_FIFO_PIO_BURST, copy_words); + edm = readl(host->ioaddr + SDEDM); + if (is_read) + words = ((edm >> 4) & 0x1f); + else + words = SDDATA_FIFO_WORDS - ((edm >> 4) & 0x1f); + + if (words < burst_words) { + int fsm_state = (edm & SDEDM_FSM_MASK); + struct device *dev = &host->pdev->dev; + + if ((is_read && + (fsm_state != SDEDM_FSM_READDATA && + fsm_state != SDEDM_FSM_READWAIT && + fsm_state != SDEDM_FSM_READCRC)) || + (!is_read && + (fsm_state != SDEDM_FSM_WRITEDATA && + fsm_state != SDEDM_FSM_WRITESTART1 && + fsm_state != SDEDM_FSM_WRITESTART2))) { + hsts = readl(host->ioaddr + SDHSTS); + dev_err(dev, "fsm %x, hsts %08x\n", + fsm_state, hsts); + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + if (time_after(jiffies, wait_max)) { + dev_err(dev, "PIO %s timeout - EDM %08x\n", + is_read ? "read" : "write", + edm); + hsts = SDHSTS_REW_TIME_OUT; + break; + } + ndelay((burst_words - words) * + host->ns_per_fifo_word); + continue; + } else if (words > copy_words) { + words = copy_words; + } + + copy_words -= words; + + while (words) { + if (is_read) + *(buf++) = readl(host->ioaddr + SDDATA); + else + writel(*(buf++), host->ioaddr + SDDATA); + words--; + } + } + + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + sg_miter_stop(&host->sg_miter); + + local_irq_restore(flags); +} + +static void bcm2835_transfer_pio(struct bcm2835_host *host) +{ + struct device *dev = &host->pdev->dev; + u32 sdhsts; + bool is_read; + + is_read = (host->data->flags & MMC_DATA_READ) != 0; + bcm2835_transfer_block_pio(host, is_read); + + sdhsts = readl(host->ioaddr + SDHSTS); + if (sdhsts & (SDHSTS_CRC16_ERROR | + SDHSTS_CRC7_ERROR | + SDHSTS_FIFO_ERROR)) { + dev_err(dev, "%s transfer error - HSTS %08x\n", + is_read ? "read" : "write", sdhsts); + host->data->error = -EILSEQ; + } else if ((sdhsts & (SDHSTS_CMD_TIME_OUT | + SDHSTS_REW_TIME_OUT))) { + dev_err(dev, "%s timeout error - HSTS %08x\n", + is_read ? "read" : "write", sdhsts); + host->data->error = -ETIMEDOUT; + } +} + +static +void bcm2835_prepare_dma(struct bcm2835_host *host, struct mmc_data *data) +{ + int len, dir_data, dir_slave; + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *dma_chan; + + dma_chan = host->dma_chan_rxtx; + if (data->flags & MMC_DATA_READ) { + dir_data = DMA_FROM_DEVICE; + dir_slave = DMA_DEV_TO_MEM; + } else { + dir_data = DMA_TO_DEVICE; + dir_slave = DMA_MEM_TO_DEV; + } + + /* The block doesn't manage the FIFO DREQs properly for + * multi-block transfers, so don't attempt to DMA the final + * few words. Unfortunately this requires the final sg entry + * to be trimmed. N.B. This code demands that the overspill + * is contained in a single sg entry. + */ + + host->drain_words = 0; + if ((data->blocks > 1) && (dir_data == DMA_FROM_DEVICE)) { + struct scatterlist *sg; + u32 len; + int i; + + len = min((u32)(FIFO_READ_THRESHOLD - 1) * 4, + (u32)data->blocks * data->blksz); + + for_each_sg(data->sg, sg, data->sg_len, i) { + if (sg_is_last(sg)) { + WARN_ON(sg->length < len); + sg->length -= len; + host->drain_page = sg_page(sg); + host->drain_offset = sg->offset + sg->length; + } + } + host->drain_words = len / 4; + } + + /* The parameters have already been validated, so this will not fail */ + (void)dmaengine_slave_config(dma_chan, + (dir_data == DMA_FROM_DEVICE) ? + &host->dma_cfg_rx : + &host->dma_cfg_tx); + + len = dma_map_sg(dma_chan->device->dev, data->sg, data->sg_len, + dir_data); + + if (len > 0) { + desc = dmaengine_prep_slave_sg(dma_chan, data->sg, + len, dir_slave, + DMA_PREP_INTERRUPT | + DMA_CTRL_ACK); + } + + if (desc) { + desc->callback = bcm2835_dma_complete; + desc->callback_param = host; + host->dma_desc = desc; + host->dma_chan = dma_chan; + host->dma_dir = dir_data; + } +} + +static void bcm2835_start_dma(struct bcm2835_host *host) +{ + dmaengine_submit(host->dma_desc); + dma_async_issue_pending(host->dma_chan); +} + +static void bcm2835_set_transfer_irqs(struct bcm2835_host *host) +{ + u32 all_irqs = SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN | + SDHCFG_BUSY_IRPT_EN; + + if (host->dma_desc) { + host->hcfg = (host->hcfg & ~all_irqs) | + SDHCFG_BUSY_IRPT_EN; + } else { + host->hcfg = (host->hcfg & ~all_irqs) | + SDHCFG_DATA_IRPT_EN | + SDHCFG_BUSY_IRPT_EN; + } + + writel(host->hcfg, host->ioaddr + SDHCFG); +} + +static +void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_command *cmd) +{ + struct mmc_data *data = cmd->data; + + WARN_ON(host->data); + + host->data = data; + if (!data) + return; + + host->data_complete = false; + host->data->bytes_xfered = 0; + + if (!host->dma_desc) { + /* Use PIO */ + int flags = SG_MITER_ATOMIC; + + if (data->flags & MMC_DATA_READ) + flags |= SG_MITER_TO_SG; + else + flags |= SG_MITER_FROM_SG; + sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); + host->blocks = data->blocks; + } + + bcm2835_set_transfer_irqs(host); + + writel(data->blksz, host->ioaddr + SDHBCT); + writel(data->blocks, host->ioaddr + SDHBLC); +} + +static u32 bcm2835_read_wait_sdcmd(struct bcm2835_host *host, u32 max_ms) +{ + struct device *dev = &host->pdev->dev; + u32 value; + int ret; + + ret = readl_poll_timeout(host->ioaddr + SDCMD, value, + !(value & SDCMD_NEW_FLAG), 1, 10); + if (ret == -ETIMEDOUT) + /* if it takes a while make poll interval bigger */ + ret = readl_poll_timeout(host->ioaddr + SDCMD, value, + !(value & SDCMD_NEW_FLAG), + 10, max_ms * 1000); + if (ret == -ETIMEDOUT) + dev_err(dev, "%s: timeout (%d ms)\n", __func__, max_ms); + + return value; +} + +static void bcm2835_finish_request(struct bcm2835_host *host) +{ + struct dma_chan *terminate_chan = NULL; + struct mmc_request *mrq; + + cancel_delayed_work(&host->timeout_work); + + mrq = host->mrq; + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + host->dma_desc = NULL; + terminate_chan = host->dma_chan; + host->dma_chan = NULL; + + if (terminate_chan) { + int err = dmaengine_terminate_all(terminate_chan); + + if (err) + dev_err(&host->pdev->dev, + "failed to terminate DMA (%d)\n", err); + } + + mmc_request_done(host->mmc, mrq); +} + +static +bool bcm2835_send_command(struct bcm2835_host *host, struct mmc_command *cmd) +{ + struct device *dev = &host->pdev->dev; + u32 sdcmd, sdhsts; + unsigned long timeout; + + WARN_ON(host->cmd); + + sdcmd = bcm2835_read_wait_sdcmd(host, 100); + if (sdcmd & SDCMD_NEW_FLAG) { + dev_err(dev, "previous command never completed.\n"); + bcm2835_dumpregs(host); + cmd->error = -EILSEQ; + bcm2835_finish_request(host); + return false; + } + + if (!cmd->data && cmd->busy_timeout > 9000) + timeout = DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ; + else + timeout = 10 * HZ; + schedule_delayed_work(&host->timeout_work, timeout); + + host->cmd = cmd; + + /* Clear any error flags */ + sdhsts = readl(host->ioaddr + SDHSTS); + if (sdhsts & SDHSTS_ERROR_MASK) + writel(sdhsts, host->ioaddr + SDHSTS); + + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { + dev_err(dev, "unsupported response type!\n"); + cmd->error = -EINVAL; + bcm2835_finish_request(host); + return false; + } + + bcm2835_prepare_data(host, cmd); + + writel(cmd->arg, host->ioaddr + SDARG); + + sdcmd = cmd->opcode & SDCMD_CMD_MASK; + + host->use_busy = false; + if (!(cmd->flags & MMC_RSP_PRESENT)) { + sdcmd |= SDCMD_NO_RESPONSE; + } else { + if (cmd->flags & MMC_RSP_136) + sdcmd |= SDCMD_LONG_RESPONSE; + if (cmd->flags & MMC_RSP_BUSY) { + sdcmd |= SDCMD_BUSYWAIT; + host->use_busy = true; + } + } + + if (cmd->data) { + if (cmd->data->flags & MMC_DATA_WRITE) + sdcmd |= SDCMD_WRITE_CMD; + if (cmd->data->flags & MMC_DATA_READ) + sdcmd |= SDCMD_READ_CMD; + } + + writel(sdcmd | SDCMD_NEW_FLAG, host->ioaddr + SDCMD); + + return true; +} + +static void bcm2835_transfer_complete(struct bcm2835_host *host) +{ + struct mmc_data *data; + + WARN_ON(!host->data_complete); + + data = host->data; + host->data = NULL; + + /* Need to send CMD12 if - + * a) open-ended multiblock transfer (no CMD23) + * b) error in multiblock transfer + */ + if (host->mrq->stop && (data->error || !host->use_sbc)) { + if (bcm2835_send_command(host, host->mrq->stop)) { + /* No busy, so poll for completion */ + if (!host->use_busy) + bcm2835_finish_command(host); + } + } else { + bcm2835_wait_transfer_complete(host); + bcm2835_finish_request(host); + } +} + +static void bcm2835_finish_data(struct bcm2835_host *host) +{ + struct device *dev = &host->pdev->dev; + struct mmc_data *data; + + data = host->data; + + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN); + writel(host->hcfg, host->ioaddr + SDHCFG); + + data->bytes_xfered = data->error ? 0 : (data->blksz * data->blocks); + + host->data_complete = true; + + if (host->cmd) { + /* Data managed to finish before the + * command completed. Make sure we do + * things in the proper order. + */ + dev_dbg(dev, "Finished early - HSTS %08x\n", + readl(host->ioaddr + SDHSTS)); + } else { + bcm2835_transfer_complete(host); + } +} + +static void bcm2835_finish_command(struct bcm2835_host *host) +{ + struct device *dev = &host->pdev->dev; + struct mmc_command *cmd = host->cmd; + u32 sdcmd; + + sdcmd = bcm2835_read_wait_sdcmd(host, 100); + + /* Check for errors */ + if (sdcmd & SDCMD_NEW_FLAG) { + dev_err(dev, "command never completed.\n"); + bcm2835_dumpregs(host); + host->cmd->error = -EIO; + bcm2835_finish_request(host); + return; + } else if (sdcmd & SDCMD_FAIL_FLAG) { + u32 sdhsts = readl(host->ioaddr + SDHSTS); + + /* Clear the errors */ + writel(SDHSTS_ERROR_MASK, host->ioaddr + SDHSTS); + + if (!(sdhsts & SDHSTS_CRC7_ERROR) || + (host->cmd->opcode != MMC_SEND_OP_COND)) { + if (sdhsts & SDHSTS_CMD_TIME_OUT) { + host->cmd->error = -ETIMEDOUT; + } else { + dev_err(dev, "unexpected command %d error\n", + host->cmd->opcode); + bcm2835_dumpregs(host); + host->cmd->error = -EILSEQ; + } + bcm2835_finish_request(host); + return; + } + } + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + int i; + + for (i = 0; i < 4; i++) { + cmd->resp[3 - i] = + readl(host->ioaddr + SDRSP0 + i * 4); + } + } else { + cmd->resp[0] = readl(host->ioaddr + SDRSP0); + } + } + + if (cmd == host->mrq->sbc) { + /* Finished CMD23, now send actual command. */ + host->cmd = NULL; + if (bcm2835_send_command(host, host->mrq->cmd)) { + if (host->data && host->dma_desc) + /* DMA transfer starts now, PIO starts + * after irq + */ + bcm2835_start_dma(host); + + if (!host->use_busy) + bcm2835_finish_command(host); + } + } else if (cmd == host->mrq->stop) { + /* Finished CMD12 */ + bcm2835_finish_request(host); + } else { + /* Processed actual command. */ + host->cmd = NULL; + if (!host->data) + bcm2835_finish_request(host); + else if (host->data_complete) + bcm2835_transfer_complete(host); + } +} + +static void bcm2835_timeout(struct work_struct *work) +{ + struct delayed_work *d = to_delayed_work(work); + struct bcm2835_host *host = + container_of(d, struct bcm2835_host, timeout_work); + struct device *dev = &host->pdev->dev; + + mutex_lock(&host->mutex); + + if (host->mrq) { + dev_err(dev, "timeout waiting for hardware interrupt.\n"); + bcm2835_dumpregs(host); + + if (host->data) { + host->data->error = -ETIMEDOUT; + bcm2835_finish_data(host); + } else { + if (host->cmd) + host->cmd->error = -ETIMEDOUT; + else + host->mrq->cmd->error = -ETIMEDOUT; + + bcm2835_finish_request(host); + } + } + + mutex_unlock(&host->mutex); +} + +static bool bcm2835_check_cmd_error(struct bcm2835_host *host, u32 intmask) +{ + struct device *dev = &host->pdev->dev; + + if (!(intmask & SDHSTS_ERROR_MASK)) + return false; + + if (!host->cmd) + return true; + + dev_err(dev, "sdhost_busy_irq: intmask %08x\n", intmask); + if (intmask & SDHSTS_CRC7_ERROR) { + host->cmd->error = -EILSEQ; + } else if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR)) { + if (host->mrq->data) + host->mrq->data->error = -EILSEQ; + else + host->cmd->error = -EILSEQ; + } else if (intmask & SDHSTS_REW_TIME_OUT) { + if (host->mrq->data) + host->mrq->data->error = -ETIMEDOUT; + else + host->cmd->error = -ETIMEDOUT; + } else if (intmask & SDHSTS_CMD_TIME_OUT) { + host->cmd->error = -ETIMEDOUT; + } + bcm2835_dumpregs(host); + return true; +} + +static void bcm2835_check_data_error(struct bcm2835_host *host, u32 intmask) +{ + if (!host->data) + return; + if (intmask & (SDHSTS_CRC16_ERROR | SDHSTS_FIFO_ERROR)) + host->data->error = -EILSEQ; + if (intmask & SDHSTS_REW_TIME_OUT) + host->data->error = -ETIMEDOUT; +} + +static void bcm2835_busy_irq(struct bcm2835_host *host) +{ + if (WARN_ON(!host->cmd)) { + bcm2835_dumpregs(host); + return; + } + + if (WARN_ON(!host->use_busy)) { + bcm2835_dumpregs(host); + return; + } + host->use_busy = false; + + bcm2835_finish_command(host); +} + +static void bcm2835_data_irq(struct bcm2835_host *host, u32 intmask) +{ + /* There are no dedicated data/space available interrupt + * status bits, so it is necessary to use the single shared + * data/space available FIFO status bits. It is therefore not + * an error to get here when there is no data transfer in + * progress. + */ + if (!host->data) + return; + + bcm2835_check_data_error(host, intmask); + if (host->data->error) + goto finished; + + if (host->data->flags & MMC_DATA_WRITE) { + /* Use the block interrupt for writes after the first block */ + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN); + host->hcfg |= SDHCFG_BLOCK_IRPT_EN; + writel(host->hcfg, host->ioaddr + SDHCFG); + bcm2835_transfer_pio(host); + } else { + bcm2835_transfer_pio(host); + host->blocks--; + if ((host->blocks == 0) || host->data->error) + goto finished; + } + return; + +finished: + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN); + writel(host->hcfg, host->ioaddr + SDHCFG); +} + +static void bcm2835_data_threaded_irq(struct bcm2835_host *host) +{ + if (!host->data) + return; + if ((host->blocks == 0) || host->data->error) + bcm2835_finish_data(host); +} + +static void bcm2835_block_irq(struct bcm2835_host *host) +{ + if (WARN_ON(!host->data)) { + bcm2835_dumpregs(host); + return; + } + + if (!host->dma_desc) { + WARN_ON(!host->blocks); + if (host->data->error || (--host->blocks == 0)) + bcm2835_finish_data(host); + else + bcm2835_transfer_pio(host); + } else if (host->data->flags & MMC_DATA_WRITE) { + bcm2835_finish_data(host); + } +} + +static irqreturn_t bcm2835_irq(int irq, void *dev_id) +{ + irqreturn_t result = IRQ_NONE; + struct bcm2835_host *host = dev_id; + u32 intmask; + + spin_lock(&host->lock); + + intmask = readl(host->ioaddr + SDHSTS); + + writel(SDHSTS_BUSY_IRPT | + SDHSTS_BLOCK_IRPT | + SDHSTS_SDIO_IRPT | + SDHSTS_DATA_FLAG, + host->ioaddr + SDHSTS); + + if (intmask & SDHSTS_BLOCK_IRPT) { + bcm2835_check_data_error(host, intmask); + host->irq_block = true; + result = IRQ_WAKE_THREAD; + } + + if (intmask & SDHSTS_BUSY_IRPT) { + if (!bcm2835_check_cmd_error(host, intmask)) { + host->irq_busy = true; + result = IRQ_WAKE_THREAD; + } else { + result = IRQ_HANDLED; + } + } + + /* There is no true data interrupt status bit, so it is + * necessary to qualify the data flag with the interrupt + * enable bit. + */ + if ((intmask & SDHSTS_DATA_FLAG) && + (host->hcfg & SDHCFG_DATA_IRPT_EN)) { + bcm2835_data_irq(host, intmask); + host->irq_data = true; + result = IRQ_WAKE_THREAD; + } + + spin_unlock(&host->lock); + + return result; +} + +static irqreturn_t bcm2835_threaded_irq(int irq, void *dev_id) +{ + struct bcm2835_host *host = dev_id; + unsigned long flags; + bool block, busy, data; + + spin_lock_irqsave(&host->lock, flags); + + block = host->irq_block; + busy = host->irq_busy; + data = host->irq_data; + host->irq_block = false; + host->irq_busy = false; + host->irq_data = false; + + spin_unlock_irqrestore(&host->lock, flags); + + mutex_lock(&host->mutex); + + if (block) + bcm2835_block_irq(host); + if (busy) + bcm2835_busy_irq(host); + if (data) + bcm2835_data_threaded_irq(host); + + mutex_unlock(&host->mutex); + + return IRQ_HANDLED; +} + +static void bcm2835_dma_complete_work(struct work_struct *work) +{ + struct bcm2835_host *host = + container_of(work, struct bcm2835_host, dma_work); + struct mmc_data *data = host->data; + + mutex_lock(&host->mutex); + + if (host->dma_chan) { + dma_unmap_sg(host->dma_chan->device->dev, + data->sg, data->sg_len, + host->dma_dir); + + host->dma_chan = NULL; + } + + if (host->drain_words) { + unsigned long flags; + void *page; + u32 *buf; + + if (host->drain_offset & PAGE_MASK) { + host->drain_page += host->drain_offset >> PAGE_SHIFT; + host->drain_offset &= ~PAGE_MASK; + } + local_irq_save(flags); + page = kmap_atomic(host->drain_page); + buf = page + host->drain_offset; + + while (host->drain_words) { + u32 edm = readl(host->ioaddr + SDEDM); + + if ((edm >> 4) & 0x1f) + *(buf++) = readl(host->ioaddr + SDDATA); + host->drain_words--; + } + + kunmap_atomic(page); + local_irq_restore(flags); + } + + bcm2835_finish_data(host); + + mutex_unlock(&host->mutex); +} + +static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) +{ + int div; + + /* The SDCDIV register has 11 bits, and holds (div - 2). But + * in data mode the max is 50MHz wihout a minimum, and only + * the bottom 3 bits are used. Since the switch over is + * automatic (unless we have marked the card as slow...), + * chosen values have to make sense in both modes. Ident mode + * must be 100-400KHz, so can range check the requested + * clock. CMD15 must be used to return to data mode, so this + * can be monitored. + * + * clock 250MHz -> 0->125MHz, 1->83.3MHz, 2->62.5MHz, 3->50.0MHz + * 4->41.7MHz, 5->35.7MHz, 6->31.3MHz, 7->27.8MHz + * + * 623->400KHz/27.8MHz + * reset value (507)->491159/50MHz + * + * BUT, the 3-bit clock divisor in data mode is too small if + * the core clock is higher than 250MHz, so instead use the + * SLOW_CARD configuration bit to force the use of the ident + * clock divisor at all times. + */ + + if (clock < 100000) { + /* Can't stop the clock, but make it as slow as possible + * to show willing + */ + host->cdiv = SDCDIV_MAX_CDIV; + writel(host->cdiv, host->ioaddr + SDCDIV); + return; + } + + div = host->max_clk / clock; + if (div < 2) + div = 2; + if ((host->max_clk / div) > clock) + div++; + div -= 2; + + if (div > SDCDIV_MAX_CDIV) + div = SDCDIV_MAX_CDIV; + + clock = host->max_clk / (div + 2); + host->mmc->actual_clock = clock; + + /* Calibrate some delays */ + + host->ns_per_fifo_word = (1000000000 / clock) * + ((host->mmc->caps & MMC_CAP_4_BIT_DATA) ? 8 : 32); + + host->cdiv = div; + writel(host->cdiv, host->ioaddr + SDCDIV); + + /* Set the timeout to 500ms */ + writel(host->mmc->actual_clock / 2, host->ioaddr + SDTOUT); +} + +static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct bcm2835_host *host = mmc_priv(mmc); + struct device *dev = &host->pdev->dev; + u32 edm, fsm; + + /* Reset the error statuses in case this is a retry */ + if (mrq->sbc) + mrq->sbc->error = 0; + if (mrq->cmd) + mrq->cmd->error = 0; + if (mrq->data) + mrq->data->error = 0; + if (mrq->stop) + mrq->stop->error = 0; + + if (mrq->data && !is_power_of_2(mrq->data->blksz)) { + dev_err(dev, "unsupported block size (%d bytes)\n", + mrq->data->blksz); + mrq->cmd->error = -EINVAL; + mmc_request_done(mmc, mrq); + return; + } + + if (host->use_dma && mrq->data && (mrq->data->blocks > PIO_THRESHOLD)) + bcm2835_prepare_dma(host, mrq->data); + + mutex_lock(&host->mutex); + + WARN_ON(host->mrq); + host->mrq = mrq; + + edm = readl(host->ioaddr + SDEDM); + fsm = edm & SDEDM_FSM_MASK; + + if ((fsm != SDEDM_FSM_IDENTMODE) && + (fsm != SDEDM_FSM_DATAMODE)) { + dev_err(dev, "previous command (%d) not complete (EDM %08x)\n", + readl(host->ioaddr + SDCMD) & SDCMD_CMD_MASK, + edm); + bcm2835_dumpregs(host); + mrq->cmd->error = -EILSEQ; + bcm2835_finish_request(host); + mutex_unlock(&host->mutex); + return; + } + + host->use_sbc = !!mrq->sbc && (host->mrq->data->flags & MMC_DATA_READ); + if (host->use_sbc) { + if (bcm2835_send_command(host, mrq->sbc)) { + if (!host->use_busy) + bcm2835_finish_command(host); + } + } else if (bcm2835_send_command(host, mrq->cmd)) { + if (host->data && host->dma_desc) { + /* DMA transfer starts now, PIO starts after irq */ + bcm2835_start_dma(host); + } + + if (!host->use_busy) + bcm2835_finish_command(host); + } + + mutex_unlock(&host->mutex); +} + +static void bcm2835_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct bcm2835_host *host = mmc_priv(mmc); + + mutex_lock(&host->mutex); + + if (!ios->clock || ios->clock != host->clock) { + bcm2835_set_clock(host, ios->clock); + host->clock = ios->clock; + } + + /* set bus width */ + host->hcfg &= ~SDHCFG_WIDE_EXT_BUS; + if (ios->bus_width == MMC_BUS_WIDTH_4) + host->hcfg |= SDHCFG_WIDE_EXT_BUS; + + host->hcfg |= SDHCFG_WIDE_INT_BUS; + + /* Disable clever clock switching, to cope with fast core clocks */ + host->hcfg |= SDHCFG_SLOW_CARD; + + writel(host->hcfg, host->ioaddr + SDHCFG); + + mutex_unlock(&host->mutex); +} + +static struct mmc_host_ops bcm2835_ops = { + .request = bcm2835_request, + .set_ios = bcm2835_set_ios, + .hw_reset = bcm2835_reset, +}; + +static int bcm2835_add_host(struct bcm2835_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct device *dev = &host->pdev->dev; + char pio_limit_string[20]; + int ret; + + mmc->f_max = host->max_clk; + mmc->f_min = host->max_clk / SDCDIV_MAX_CDIV; + + mmc->max_busy_timeout = ~0 / (mmc->f_max / 1000); + + dev_dbg(dev, "f_max %d, f_min %d, max_busy_timeout %d\n", + mmc->f_max, mmc->f_min, mmc->max_busy_timeout); + + /* host controller capabilities */ + mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_NEEDS_POLL | MMC_CAP_HW_RESET | MMC_CAP_ERASE | + MMC_CAP_CMD23; + + spin_lock_init(&host->lock); + mutex_init(&host->mutex); + + if (IS_ERR_OR_NULL(host->dma_chan_rxtx)) { + dev_warn(dev, "unable to initialise DMA channel. Falling back to PIO\n"); + host->use_dma = false; + } else { + host->use_dma = true; + + host->dma_cfg_tx.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_cfg_tx.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_cfg_tx.slave_id = 13; /* DREQ channel */ + host->dma_cfg_tx.direction = DMA_MEM_TO_DEV; + host->dma_cfg_tx.src_addr = 0; + host->dma_cfg_tx.dst_addr = host->phys_addr + SDDATA; + + host->dma_cfg_rx.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_cfg_rx.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_cfg_rx.slave_id = 13; /* DREQ channel */ + host->dma_cfg_rx.direction = DMA_DEV_TO_MEM; + host->dma_cfg_rx.src_addr = host->phys_addr + SDDATA; + host->dma_cfg_rx.dst_addr = 0; + + if (dmaengine_slave_config(host->dma_chan_rxtx, + &host->dma_cfg_tx) != 0 || + dmaengine_slave_config(host->dma_chan_rxtx, + &host->dma_cfg_rx) != 0) + host->use_dma = false; + } + + mmc->max_segs = 128; + mmc->max_req_size = 524288; + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_size = 1024; + mmc->max_blk_count = 65535; + + /* report supported voltage ranges */ + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + INIT_WORK(&host->dma_work, bcm2835_dma_complete_work); + INIT_DELAYED_WORK(&host->timeout_work, bcm2835_timeout); + + /* Set interrupt enables */ + host->hcfg = SDHCFG_BUSY_IRPT_EN; + + bcm2835_reset_internal(host); + + ret = request_threaded_irq(host->irq, bcm2835_irq, + bcm2835_threaded_irq, + 0, mmc_hostname(mmc), host); + if (ret) { + dev_err(dev, "failed to request IRQ %d: %d\n", host->irq, ret); + return ret; + } + + ret = mmc_add_host(mmc); + if (ret) { + free_irq(host->irq, host); + return ret; + } + + pio_limit_string[0] = '\0'; + if (host->use_dma && (PIO_THRESHOLD > 0)) + sprintf(pio_limit_string, " (>%d)", PIO_THRESHOLD); + dev_info(dev, "loaded - DMA %s%s\n", + host->use_dma ? "enabled" : "disabled", pio_limit_string); + + return 0; +} + +static int bcm2835_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct clk *clk; + struct resource *iomem; + struct bcm2835_host *host; + struct mmc_host *mmc; + const __be32 *regaddr_p; + int ret; + + dev_dbg(dev, "%s\n", __func__); + mmc = mmc_alloc_host(sizeof(*host), dev); + if (!mmc) + return -ENOMEM; + + mmc->ops = &bcm2835_ops; + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdev = pdev; + spin_lock_init(&host->lock); + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->ioaddr = devm_ioremap_resource(dev, iomem); + if (IS_ERR(host->ioaddr)) { + ret = PTR_ERR(host->ioaddr); + goto err; + } + + /* Parse OF address directly to get the physical address for + * DMA to our registers. + */ + regaddr_p = of_get_address(pdev->dev.of_node, 0, NULL, NULL); + if (!regaddr_p) { + dev_err(dev, "Can't get phys address\n"); + ret = -EINVAL; + goto err; + } + + host->phys_addr = be32_to_cpup(regaddr_p); + + host->dma_chan = NULL; + host->dma_desc = NULL; + + host->dma_chan_rxtx = dma_request_slave_channel(dev, "rx-tx"); + + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "could not get clk: %d\n", ret); + goto err; + } + + host->max_clk = clk_get_rate(clk); + + host->irq = platform_get_irq(pdev, 0); + if (host->irq <= 0) { + dev_err(dev, "get IRQ failed\n"); + ret = -EINVAL; + goto err; + } + + ret = mmc_of_parse(mmc); + if (ret) + goto err; + + ret = bcm2835_add_host(host); + if (ret) + goto err; + + platform_set_drvdata(pdev, host); + + dev_dbg(dev, "%s -> OK\n", __func__); + + return 0; + +err: + dev_dbg(dev, "%s -> err %d\n", __func__, ret); + mmc_free_host(mmc); + + return ret; +} + +static int bcm2835_remove(struct platform_device *pdev) +{ + struct bcm2835_host *host = platform_get_drvdata(pdev); + + mmc_remove_host(host->mmc); + + writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD); + + free_irq(host->irq, host); + + cancel_work_sync(&host->dma_work); + cancel_delayed_work_sync(&host->timeout_work); + + mmc_free_host(host->mmc); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id bcm2835_match[] = { + { .compatible = "brcm,bcm2835-sdhost" }, + { } +}; +MODULE_DEVICE_TABLE(of, bcm2835_match); + +static struct platform_driver bcm2835_driver = { + .probe = bcm2835_probe, + .remove = bcm2835_remove, + .driver = { + .name = "sdhost-bcm2835", + .of_match_table = bcm2835_match, + }, +}; +module_platform_driver(bcm2835_driver); + +MODULE_ALIAS("platform:sdhost-bcm2835"); +MODULE_DESCRIPTION("BCM2835 SDHost driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Phil Elwell"); From 5a436cc0af6202265de0d2d2d35f1b4fd85caf3b Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:31 +0200 Subject: [PATCH 038/148] mmc: sdhci: Optimize delay loops The delay loops for reset and clock enable always take at least 1 ms because they use mdelay(1). However they can take a lot less time e.g. less than 50us. Use ktime and reduce the delay to 10 microseconds per loop. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 63bc33a54d0d..777c82efce1d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -14,6 +14,7 @@ */ #include +#include #include #include #include @@ -165,7 +166,7 @@ static void sdhci_runtime_pm_bus_off(struct sdhci_host *host) void sdhci_reset(struct sdhci_host *host, u8 mask) { - unsigned long timeout; + ktime_t timeout; sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET); @@ -177,18 +178,17 @@ void sdhci_reset(struct sdhci_host *host, u8 mask) } /* Wait max 100 ms */ - timeout = 100; + timeout = ktime_add_ms(ktime_get(), 100); /* hw clears the bit when it's done */ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { - if (timeout == 0) { + if (ktime_after(ktime_get(), timeout)) { pr_err("%s: Reset 0x%x never completed.\n", mmc_hostname(host->mmc), (int)mask); sdhci_dumpregs(host); return; } - timeout--; - mdelay(1); + udelay(10); } } EXPORT_SYMBOL_GPL(sdhci_reset); @@ -1346,24 +1346,23 @@ EXPORT_SYMBOL_GPL(sdhci_calc_clk); void sdhci_enable_clk(struct sdhci_host *host, u16 clk) { - unsigned long timeout; + ktime_t timeout; clk |= SDHCI_CLOCK_INT_EN; sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); /* Wait max 20 ms */ - timeout = 20; + timeout = ktime_add_ms(ktime_get(), 20); while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) { - if (timeout == 0) { + if (ktime_after(ktime_get(), timeout)) { pr_err("%s: Internal clock never stabilised.\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; } - timeout--; spin_unlock_irq(&host->lock); - usleep_range(900, 1100); + udelay(10); spin_lock_irq(&host->lock); } From d38dcad4e7b48f3d68d5d058f8f4f52a99862e6e Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:32 +0200 Subject: [PATCH 039/148] mmc: sdhci: Let drivers decide whether to use mmc_retune_needed() with pm Devices might save and restore tuning values so that re-tuning might not be needed after a pm transition. Let drivers decide by pushing the mmc_retune_needed() logic down to them. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-acpi.c | 12 ++++++++++-- drivers/mmc/host/sdhci-brcmstb.c | 3 +++ drivers/mmc/host/sdhci-esdhc-imx.c | 6 ++++++ drivers/mmc/host/sdhci-of-arasan.c | 3 +++ drivers/mmc/host/sdhci-of-at91.c | 3 +++ drivers/mmc/host/sdhci-of-esdhc.c | 3 +++ drivers/mmc/host/sdhci-pci-core.c | 22 ++++++++++++++++++---- drivers/mmc/host/sdhci-pci.h | 2 ++ drivers/mmc/host/sdhci-pltfm.c | 3 +++ drivers/mmc/host/sdhci-pxav3.c | 5 +++++ drivers/mmc/host/sdhci-s3c.c | 6 ++++++ drivers/mmc/host/sdhci-sirf.c | 3 +++ drivers/mmc/host/sdhci-spear.c | 3 +++ drivers/mmc/host/sdhci-st.c | 6 +++++- drivers/mmc/host/sdhci.c | 4 ---- 15 files changed, 73 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/sdhci-acpi.c b/drivers/mmc/host/sdhci-acpi.c index 9dcb7048e3b1..665c5f3009b4 100644 --- a/drivers/mmc/host/sdhci-acpi.c +++ b/drivers/mmc/host/sdhci-acpi.c @@ -524,8 +524,12 @@ static int sdhci_acpi_remove(struct platform_device *pdev) static int sdhci_acpi_suspend(struct device *dev) { struct sdhci_acpi_host *c = dev_get_drvdata(dev); + struct sdhci_host *host = c->host; - return sdhci_suspend_host(c->host); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + + return sdhci_suspend_host(host); } static int sdhci_acpi_resume(struct device *dev) @@ -544,8 +548,12 @@ static int sdhci_acpi_resume(struct device *dev) static int sdhci_acpi_runtime_suspend(struct device *dev) { struct sdhci_acpi_host *c = dev_get_drvdata(dev); + struct sdhci_host *host = c->host; - return sdhci_runtime_suspend_host(c->host); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + + return sdhci_runtime_suspend_host(host); } static int sdhci_acpi_runtime_resume(struct device *dev) diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c index 159f6f64c68e..242c5dc7a81e 100644 --- a/drivers/mmc/host/sdhci-brcmstb.c +++ b/drivers/mmc/host/sdhci-brcmstb.c @@ -29,6 +29,9 @@ static int sdhci_brcmstb_suspend(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); int res; + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + res = sdhci_suspend_host(host); if (res) return res; diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 445fc47dc3e7..abad67aeabdd 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -1323,6 +1323,9 @@ static int sdhci_esdhc_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + return sdhci_suspend_host(host); } @@ -1347,6 +1350,9 @@ static int sdhci_esdhc_runtime_suspend(struct device *dev) ret = sdhci_runtime_suspend_host(host); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + if (!sdhci_sdio_irq_enabled(host)) { clk_disable_unprepare(imx_data->clk_per); clk_disable_unprepare(imx_data->clk_ipg); diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c index 1cfd7f900339..28feb5367c14 100644 --- a/drivers/mmc/host/sdhci-of-arasan.c +++ b/drivers/mmc/host/sdhci-of-arasan.c @@ -315,6 +315,9 @@ static int sdhci_arasan_suspend(struct device *dev) struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); int ret; + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + ret = sdhci_suspend_host(host); if (ret) return ret; diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c index d5430ed02a67..3f08b7ebec43 100644 --- a/drivers/mmc/host/sdhci-of-at91.c +++ b/drivers/mmc/host/sdhci-of-at91.c @@ -140,6 +140,9 @@ static int sdhci_at91_runtime_suspend(struct device *dev) ret = sdhci_runtime_suspend_host(host); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + clk_disable_unprepare(priv->gck); clk_disable_unprepare(priv->hclock); clk_disable_unprepare(priv->mainck); diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index d3aa67142839..ff37e7459386 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -528,6 +528,9 @@ static int esdhc_of_suspend(struct device *dev) esdhc_proctl = sdhci_readl(host, SDHCI_HOST_CONTROL); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + return sdhci_suspend_host(host); } diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 86560d590786..b7150e935fb6 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -1653,6 +1653,7 @@ static int sdhci_pci_suspend(struct device *dev) struct pci_dev *pdev = to_pci_dev(dev); struct sdhci_pci_chip *chip; struct sdhci_pci_slot *slot; + struct sdhci_host *host; mmc_pm_flag_t slot_pm_flags; mmc_pm_flag_t pm_flags = 0; int i, ret; @@ -1666,14 +1667,19 @@ static int sdhci_pci_suspend(struct device *dev) if (!slot) continue; - ret = sdhci_suspend_host(slot->host); + host = slot->host; + + if (chip->pm_retune && host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + + ret = sdhci_suspend_host(host); if (ret) goto err_pci_suspend; - slot_pm_flags = slot->host->mmc->pm_flags; + slot_pm_flags = host->mmc->pm_flags; if (slot_pm_flags & MMC_PM_WAKE_SDIO_IRQ) - sdhci_enable_irq_wakeups(slot->host); + sdhci_enable_irq_wakeups(host); pm_flags |= slot_pm_flags; } @@ -1737,6 +1743,7 @@ static int sdhci_pci_runtime_suspend(struct device *dev) struct pci_dev *pdev = to_pci_dev(dev); struct sdhci_pci_chip *chip; struct sdhci_pci_slot *slot; + struct sdhci_host *host; int i, ret; chip = pci_get_drvdata(pdev); @@ -1748,10 +1755,15 @@ static int sdhci_pci_runtime_suspend(struct device *dev) if (!slot) continue; - ret = sdhci_runtime_suspend_host(slot->host); + host = slot->host; + ret = sdhci_runtime_suspend_host(host); if (ret) goto err_pci_runtime_suspend; + + if (chip->rpm_retune && + host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); } if (chip->fixes && chip->fixes->suspend) { @@ -2042,6 +2054,8 @@ static int sdhci_pci_probe(struct pci_dev *pdev, chip->allow_runtime_pm = chip->fixes->allow_runtime_pm; } chip->num_slots = slots; + chip->pm_retune = true; + chip->rpm_retune = true; pci_set_drvdata(pdev, chip); diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index 36f743464fcc..e6e916b3361e 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -97,6 +97,8 @@ struct sdhci_pci_chip { unsigned int quirks; unsigned int quirks2; bool allow_runtime_pm; + bool pm_retune; + bool rpm_retune; const struct sdhci_pci_fixes *fixes; int num_slots; /* Slots on controller */ diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index ad49bfaf5bf8..e090d8c42ddb 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c @@ -213,6 +213,9 @@ static int sdhci_pltfm_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + return sdhci_suspend_host(host); } diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index 132670a612a0..22ed90433d29 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -527,6 +527,8 @@ static int sdhci_pxav3_suspend(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); pm_runtime_get_sync(dev); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); ret = sdhci_suspend_host(host); pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); @@ -560,6 +562,9 @@ static int sdhci_pxav3_runtime_suspend(struct device *dev) if (ret) return ret; + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + clk_disable_unprepare(pxa->clk_io); if (!IS_ERR(pxa->clk_core)) clk_disable_unprepare(pxa->clk_core); diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 3e5c83d435ae..d02284da2ec6 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -743,6 +743,9 @@ static int sdhci_s3c_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + return sdhci_suspend_host(host); } @@ -764,6 +767,9 @@ static int sdhci_s3c_runtime_suspend(struct device *dev) ret = sdhci_runtime_suspend_host(host); + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + if (ourhost->cur_clk >= 0) clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]); clk_disable_unprepare(busclk); diff --git a/drivers/mmc/host/sdhci-sirf.c b/drivers/mmc/host/sdhci-sirf.c index 5d068639dd3f..c251c6c0a112 100644 --- a/drivers/mmc/host/sdhci-sirf.c +++ b/drivers/mmc/host/sdhci-sirf.c @@ -237,6 +237,9 @@ static int sdhci_sirf_suspend(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); int ret; + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + ret = sdhci_suspend_host(host); if (ret) return ret; diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index 255a896769b8..8c0f88428556 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -165,6 +165,9 @@ static int sdhci_suspend(struct device *dev) struct spear_sdhci *sdhci = sdhci_priv(host); int ret; + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + ret = sdhci_suspend_host(host); if (!ret) clk_disable(sdhci->clk); diff --git a/drivers/mmc/host/sdhci-st.c b/drivers/mmc/host/sdhci-st.c index 7fff6490855c..68c36c9fa231 100644 --- a/drivers/mmc/host/sdhci-st.c +++ b/drivers/mmc/host/sdhci-st.c @@ -463,8 +463,12 @@ static int sdhci_st_suspend(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct st_mmc_platform_data *pdata = sdhci_pltfm_priv(pltfm_host); - int ret = sdhci_suspend_host(host); + int ret; + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + + ret = sdhci_suspend_host(host); if (ret) goto out; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 777c82efce1d..105f4448145d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2857,8 +2857,6 @@ int sdhci_suspend_host(struct sdhci_host *host) sdhci_disable_card_detection(host); mmc_retune_timer_stop(host->mmc); - if (host->tuning_mode != SDHCI_TUNING_MODE_3) - mmc_retune_needed(host->mmc); if (!device_may_wakeup(mmc_dev(host->mmc))) { host->ier = 0; @@ -2919,8 +2917,6 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host) unsigned long flags; mmc_retune_timer_stop(host->mmc); - if (host->tuning_mode != SDHCI_TUNING_MODE_3) - mmc_retune_needed(host->mmc); spin_lock_irqsave(&host->lock, flags); host->ier &= SDHCI_INT_CARD_INT; From ac9f67b5800b9a96987ec602a34dd256a92ac7ca Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:33 +0200 Subject: [PATCH 040/148] mmc: sdhci-pci: Let devices define their own private data Let devices define their own private data to facilitate device-specific operations. The size of the private structure is specified in the sdhci_pci_fixes structure, then sdhci_pci_probe_slot() will allocate extra space for it, and sdhci_pci_priv() can be used to get a reference to it. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 3 ++- drivers/mmc/host/sdhci-pci.h | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index b7150e935fb6..84b73cb22940 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -1830,6 +1830,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( struct sdhci_pci_slot *slot; struct sdhci_host *host; int ret, bar = first_bar + slotno; + size_t priv_size = chip->fixes ? chip->fixes->priv_size : 0; if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) { dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar); @@ -1851,7 +1852,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( return ERR_PTR(-ENODEV); } - host = sdhci_alloc_host(&pdev->dev, sizeof(struct sdhci_pci_slot)); + host = sdhci_alloc_host(&pdev->dev, sizeof(*slot) + priv_size); if (IS_ERR(host)) { dev_err(&pdev->dev, "cannot allocate host\n"); return ERR_CAST(host); diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index e6e916b3361e..cfe519c0c990 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -70,6 +70,7 @@ struct sdhci_pci_fixes { int (*resume) (struct sdhci_pci_chip *); const struct sdhci_ops *ops; + size_t priv_size; }; struct sdhci_pci_slot { @@ -89,6 +90,7 @@ struct sdhci_pci_slot { struct mmc_card *card, unsigned int max_dtr, int host_drv, int card_drv, int *drv_type); + unsigned long private[0] ____cacheline_aligned; }; struct sdhci_pci_chip { @@ -105,4 +107,9 @@ struct sdhci_pci_chip { struct sdhci_pci_slot *slots[MAX_SLOTS]; /* Pointers to host slots */ }; +static inline void *sdhci_pci_priv(struct sdhci_pci_slot *slot) +{ + return (void *)slot->private; +} + #endif /* __SDHCI_PCI_H */ From c959a6b00ff589c652373e27bced5a700bd450f2 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:34 +0200 Subject: [PATCH 041/148] mmc: sdhci-pci: Don't re-tune with runtime pm for some Intel devices Make use of an Intel ACPI _DSM that indicates if re-tuning is needed after D3. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 90 +++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 84b73cb22940..3564327c9c7b 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -259,6 +259,77 @@ static const struct sdhci_pci_fixes sdhci_intel_pch_sdio = { .probe_slot = pch_hc_probe_slot, }; +enum { + INTEL_DSM_FNS = 0, + INTEL_DSM_D3_RETUNE = 10, +}; + +struct intel_host { + u32 dsm_fns; + bool d3_retune; +}; + +const u8 intel_dsm_uuid[] = { + 0xA5, 0x3E, 0xC1, 0xF6, 0xCD, 0x65, 0x1F, 0x46, + 0xAB, 0x7A, 0x29, 0xF7, 0xE8, 0xD5, 0xBD, 0x61, +}; + +static int __intel_dsm(struct intel_host *intel_host, struct device *dev, + unsigned int fn, u32 *result) +{ + union acpi_object *obj; + int err = 0; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(dev), intel_dsm_uuid, 0, fn, NULL); + if (!obj) + return -EOPNOTSUPP; + + if (obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1) { + err = -EINVAL; + goto out; + } + + if (obj->buffer.length >= 4) + *result = *(u32 *)obj->buffer.pointer; + else if (obj->buffer.length >= 2) + *result = *(u16 *)obj->buffer.pointer; + else + *result = *(u8 *)obj->buffer.pointer; +out: + ACPI_FREE(obj); + + return err; +} + +static int intel_dsm(struct intel_host *intel_host, struct device *dev, + unsigned int fn, u32 *result) +{ + if (fn > 31 || !(intel_host->dsm_fns & (1 << fn))) + return -EOPNOTSUPP; + + return __intel_dsm(intel_host, dev, fn, result); +} + +static void intel_dsm_init(struct intel_host *intel_host, struct device *dev, + struct mmc_host *mmc) +{ + int err; + u32 val; + + err = __intel_dsm(intel_host, dev, INTEL_DSM_FNS, &intel_host->dsm_fns); + if (err) { + pr_debug("%s: DSM not supported, error %d\n", + mmc_hostname(mmc), err); + return; + } + + pr_debug("%s: DSM function mask %#x\n", + mmc_hostname(mmc), intel_host->dsm_fns); + + err = intel_dsm(intel_host, dev, INTEL_DSM_D3_RETUNE, &val); + intel_host->d3_retune = err ? true : !!val; +} + static void sdhci_pci_int_hw_reset(struct sdhci_host *host) { u8 reg; @@ -359,8 +430,19 @@ out: return ret; } +static void byt_read_dsm(struct sdhci_pci_slot *slot) +{ + struct intel_host *intel_host = sdhci_pci_priv(slot); + struct device *dev = &slot->chip->pdev->dev; + struct mmc_host *mmc = slot->host->mmc; + + intel_dsm_init(intel_host, dev, mmc); + slot->chip->rpm_retune = intel_host->d3_retune; +} + static int byt_emmc_probe_slot(struct sdhci_pci_slot *slot) { + byt_read_dsm(slot); slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE | MMC_CAP_HW_RESET | MMC_CAP_1_8V_DDR | MMC_CAP_CMD_DURING_TFR | @@ -405,6 +487,8 @@ static int ni_byt_sdio_probe_slot(struct sdhci_pci_slot *slot) { int err; + byt_read_dsm(slot); + err = ni_set_max_freq(slot); if (err) return err; @@ -416,6 +500,7 @@ static int ni_byt_sdio_probe_slot(struct sdhci_pci_slot *slot) static int byt_sdio_probe_slot(struct sdhci_pci_slot *slot) { + byt_read_dsm(slot); slot->host->mmc->caps |= MMC_CAP_POWER_OFF_CARD | MMC_CAP_NONREMOVABLE | MMC_CAP_WAIT_WHILE_BUSY; return 0; @@ -423,6 +508,7 @@ static int byt_sdio_probe_slot(struct sdhci_pci_slot *slot) static int byt_sd_probe_slot(struct sdhci_pci_slot *slot) { + byt_read_dsm(slot); slot->host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; slot->cd_idx = 0; slot->cd_override_level = true; @@ -488,6 +574,7 @@ static const struct sdhci_pci_fixes sdhci_intel_byt_emmc = { SDHCI_QUIRK2_CAPS_BIT63_FOR_HS400 | SDHCI_QUIRK2_STOP_WITH_TC, .ops = &sdhci_intel_byt_ops, + .priv_size = sizeof(struct intel_host), }; static const struct sdhci_pci_fixes sdhci_ni_byt_sdio = { @@ -497,6 +584,7 @@ static const struct sdhci_pci_fixes sdhci_ni_byt_sdio = { .allow_runtime_pm = true, .probe_slot = ni_byt_sdio_probe_slot, .ops = &sdhci_intel_byt_ops, + .priv_size = sizeof(struct intel_host), }; static const struct sdhci_pci_fixes sdhci_intel_byt_sdio = { @@ -506,6 +594,7 @@ static const struct sdhci_pci_fixes sdhci_intel_byt_sdio = { .allow_runtime_pm = true, .probe_slot = byt_sdio_probe_slot, .ops = &sdhci_intel_byt_ops, + .priv_size = sizeof(struct intel_host), }; static const struct sdhci_pci_fixes sdhci_intel_byt_sd = { @@ -517,6 +606,7 @@ static const struct sdhci_pci_fixes sdhci_intel_byt_sd = { .own_cd_for_runtime_pm = true, .probe_slot = byt_sd_probe_slot, .ops = &sdhci_intel_byt_ops, + .priv_size = sizeof(struct intel_host), }; /* Define Host controllers for Intel Merrifield platform */ From 51ced59cc02e0d08f61772c888480b9b1749cef1 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:35 +0200 Subject: [PATCH 042/148] mmc: sdhci-pci: Use ACPI DSM to get driver strength for some Intel devices Make use of an Intel ACPI _DSM that provides eMMC driver strength. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 77 ++++++------------------------- drivers/mmc/host/sdhci-pci-data.c | 3 -- 2 files changed, 14 insertions(+), 66 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 3564327c9c7b..351920f4ec42 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -261,11 +261,13 @@ static const struct sdhci_pci_fixes sdhci_intel_pch_sdio = { enum { INTEL_DSM_FNS = 0, + INTEL_DSM_DRV_STRENGTH = 9, INTEL_DSM_D3_RETUNE = 10, }; struct intel_host { u32 dsm_fns; + int drv_strength; bool d3_retune; }; @@ -326,6 +328,9 @@ static void intel_dsm_init(struct intel_host *intel_host, struct device *dev, pr_debug("%s: DSM function mask %#x\n", mmc_hostname(mmc), intel_host->dsm_fns); + err = intel_dsm(intel_host, dev, INTEL_DSM_DRV_STRENGTH, &val); + intel_host->drv_strength = err ? 0 : val; + err = intel_dsm(intel_host, dev, INTEL_DSM_D3_RETUNE, &val); intel_host->d3_retune = err ? true : !!val; } @@ -345,67 +350,15 @@ static void sdhci_pci_int_hw_reset(struct sdhci_host *host) usleep_range(300, 1000); } -static int spt_select_drive_strength(struct sdhci_host *host, - struct mmc_card *card, - unsigned int max_dtr, - int host_drv, int card_drv, int *drv_type) +static int intel_select_drive_strength(struct mmc_card *card, + unsigned int max_dtr, int host_drv, + int card_drv, int *drv_type) { - int drive_strength; + struct sdhci_host *host = mmc_priv(card->host); + struct sdhci_pci_slot *slot = sdhci_priv(host); + struct intel_host *intel_host = sdhci_pci_priv(slot); - if (sdhci_pci_spt_drive_strength > 0) - drive_strength = sdhci_pci_spt_drive_strength & 0xf; - else - drive_strength = 0; /* Default 50-ohm */ - - if ((mmc_driver_type_mask(drive_strength) & card_drv) == 0) - drive_strength = 0; /* Default 50-ohm */ - - return drive_strength; -} - -/* Try to read the drive strength from the card */ -static void spt_read_drive_strength(struct sdhci_host *host) -{ - u32 val, i, t; - u16 m; - - if (sdhci_pci_spt_drive_strength) - return; - - sdhci_pci_spt_drive_strength = -1; - - m = sdhci_readw(host, SDHCI_HOST_CONTROL2) & 0x7; - if (m != 3 && m != 5) - return; - val = sdhci_readl(host, SDHCI_PRESENT_STATE); - if (val & 0x3) - return; - sdhci_writel(host, 0x007f0023, SDHCI_INT_ENABLE); - sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); - sdhci_writew(host, 0x10, SDHCI_TRANSFER_MODE); - sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL); - sdhci_writew(host, 512, SDHCI_BLOCK_SIZE); - sdhci_writew(host, 1, SDHCI_BLOCK_COUNT); - sdhci_writel(host, 0, SDHCI_ARGUMENT); - sdhci_writew(host, 0x83b, SDHCI_COMMAND); - for (i = 0; i < 1000; i++) { - val = sdhci_readl(host, SDHCI_INT_STATUS); - if (val & 0xffff8000) - return; - if (val & 0x20) - break; - udelay(1); - } - val = sdhci_readl(host, SDHCI_PRESENT_STATE); - if (!(val & 0x800)) - return; - for (i = 0; i < 47; i++) - val = sdhci_readl(host, SDHCI_BUFFER); - t = val & 0xf00; - if (t != 0x200 && t != 0x300) - return; - - sdhci_pci_spt_drive_strength = 0x10 | ((val >> 12) & 0xf); + return intel_host->drv_strength; } static int bxt_get_cd(struct mmc_host *mmc) @@ -451,10 +404,8 @@ static int byt_emmc_probe_slot(struct sdhci_pci_slot *slot) slot->hw_reset = sdhci_pci_int_hw_reset; if (slot->chip->pdev->device == PCI_DEVICE_ID_INTEL_BSW_EMMC) slot->host->timeout_clk = 1000; /* 1000 kHz i.e. 1 MHz */ - if (slot->chip->pdev->device == PCI_DEVICE_ID_INTEL_SPT_EMMC) { - spt_read_drive_strength(slot->host); - slot->select_drive_strength = spt_select_drive_strength; - } + slot->host->mmc_host_ops.select_drive_strength = + intel_select_drive_strength; return 0; } diff --git a/drivers/mmc/host/sdhci-pci-data.c b/drivers/mmc/host/sdhci-pci-data.c index 56fddc622a54..a611217769f5 100644 --- a/drivers/mmc/host/sdhci-pci-data.c +++ b/drivers/mmc/host/sdhci-pci-data.c @@ -3,6 +3,3 @@ struct sdhci_pci_data *(*sdhci_pci_get_data)(struct pci_dev *pdev, int slotno); EXPORT_SYMBOL_GPL(sdhci_pci_get_data); - -int sdhci_pci_spt_drive_strength; -EXPORT_SYMBOL_GPL(sdhci_pci_spt_drive_strength); From 0c62e6752d0c769aae41bfade6258e5f9aedbe2d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:36 +0200 Subject: [PATCH 043/148] mmc: sdhci: Remove ->select_drive_strength() callback Drivers can use the host operation directly, so remove this now unused callback. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 20 -------------------- drivers/mmc/host/sdhci-pci.h | 4 ---- drivers/mmc/host/sdhci.c | 14 -------------- drivers/mmc/host/sdhci.h | 4 ---- 4 files changed, 42 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 351920f4ec42..5c620206138e 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -36,10 +36,6 @@ static int sdhci_pci_enable_dma(struct sdhci_host *host); static void sdhci_pci_set_bus_width(struct sdhci_host *host, int width); static void sdhci_pci_hw_reset(struct sdhci_host *host); -static int sdhci_pci_select_drive_strength(struct sdhci_host *host, - struct mmc_card *card, - unsigned int max_dtr, int host_drv, - int card_drv, int *drv_type); /*****************************************************************************\ * * @@ -514,7 +510,6 @@ static const struct sdhci_ops sdhci_intel_byt_ops = { .reset = sdhci_reset, .set_uhs_signaling = sdhci_set_uhs_signaling, .hw_reset = sdhci_pci_hw_reset, - .select_drive_strength = sdhci_pci_select_drive_strength, }; static const struct sdhci_pci_fixes sdhci_intel_byt_emmc = { @@ -1658,20 +1653,6 @@ static void sdhci_pci_hw_reset(struct sdhci_host *host) slot->hw_reset(host); } -static int sdhci_pci_select_drive_strength(struct sdhci_host *host, - struct mmc_card *card, - unsigned int max_dtr, int host_drv, - int card_drv, int *drv_type) -{ - struct sdhci_pci_slot *slot = sdhci_priv(host); - - if (!slot->select_drive_strength) - return 0; - - return slot->select_drive_strength(host, card, max_dtr, host_drv, - card_drv, drv_type); -} - static const struct sdhci_ops sdhci_pci_ops = { .set_clock = sdhci_set_clock, .enable_dma = sdhci_pci_enable_dma, @@ -1679,7 +1660,6 @@ static const struct sdhci_ops sdhci_pci_ops = { .reset = sdhci_reset, .set_uhs_signaling = sdhci_set_uhs_signaling, .hw_reset = sdhci_pci_hw_reset, - .select_drive_strength = sdhci_pci_select_drive_strength, }; /*****************************************************************************\ diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index cfe519c0c990..da7b95016c6f 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -86,10 +86,6 @@ struct sdhci_pci_slot { bool cd_override_level; void (*hw_reset)(struct sdhci_host *host); - int (*select_drive_strength)(struct sdhci_host *host, - struct mmc_card *card, - unsigned int max_dtr, int host_drv, - int card_drv, int *drv_type); unsigned long private[0] ____cacheline_aligned; }; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 105f4448145d..e055bd3f5fe8 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2182,19 +2182,6 @@ out_unlock: } EXPORT_SYMBOL_GPL(sdhci_execute_tuning); -static int sdhci_select_drive_strength(struct mmc_card *card, - unsigned int max_dtr, int host_drv, - int card_drv, int *drv_type) -{ - struct sdhci_host *host = mmc_priv(card->host); - - if (!host->ops->select_drive_strength) - return 0; - - return host->ops->select_drive_strength(host, card, max_dtr, host_drv, - card_drv, drv_type); -} - static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable) { /* Host Controller v3.00 defines preset value registers */ @@ -2308,7 +2295,6 @@ static const struct mmc_host_ops sdhci_ops = { .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, .prepare_hs400_tuning = sdhci_prepare_hs400_tuning, .execute_tuning = sdhci_execute_tuning, - .select_drive_strength = sdhci_select_drive_strength, .card_event = sdhci_card_event, .card_busy = sdhci_card_busy, }; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index edf3adfbc213..67772674da90 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -562,10 +562,6 @@ struct sdhci_ops { void (*adma_workaround)(struct sdhci_host *host, u32 intmask); void (*card_event)(struct sdhci_host *host); void (*voltage_switch)(struct sdhci_host *host); - int (*select_drive_strength)(struct sdhci_host *host, - struct mmc_card *card, - unsigned int max_dtr, int host_drv, - int card_drv, int *drv_type); }; #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS From d1e4f74f911daee4d69b5fd0c81902f7d37de579 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:37 +0200 Subject: [PATCH 044/148] mmc: sdhci: Do not use spin lock in set_ios paths The spin lock is not necessary in set_ios. Anything that is racing with changes to the I/O state is already broken. The mmc core already provides synchronization via "claiming" the host. So remove spin_lock and friends from sdhci_set_ios and related callbacks. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-msm.c | 8 -------- drivers/mmc/host/sdhci-of-arasan.c | 6 ------ drivers/mmc/host/sdhci-of-at91.c | 2 -- drivers/mmc/host/sdhci-pci-core.c | 4 ---- drivers/mmc/host/sdhci-pxav3.c | 5 +---- drivers/mmc/host/sdhci-s3c.c | 4 ---- drivers/mmc/host/sdhci.c | 9 --------- 7 files changed, 1 insertion(+), 37 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 10cdc84d5113..9d601dc0d646 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -991,12 +991,8 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, mmc_hostname(host->mmc), host->clock, uhs, ctrl_2); sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); - spin_unlock_irq(&host->lock); - if (mmc->ios.timing == MMC_TIMING_MMC_HS400) sdhci_msm_hs400(host, &mmc->ios); - - spin_lock_irq(&host->lock); } static void sdhci_msm_voltage_switch(struct sdhci_host *host) @@ -1089,13 +1085,9 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) goto out; } - spin_unlock_irq(&host->lock); - sdhci_msm_hc_select_mode(host); msm_set_clock_rate_for_bus_mode(host, clock); - - spin_lock_irq(&host->lock); out: __sdhci_msm_set_clock(host, clock); } diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c index 28feb5367c14..61accf0d4739 100644 --- a/drivers/mmc/host/sdhci-of-arasan.c +++ b/drivers/mmc/host/sdhci-of-arasan.c @@ -194,9 +194,7 @@ static void sdhci_arasan_set_clock(struct sdhci_host *host, unsigned int clock) * through low speeds without power cycling. */ sdhci_set_clock(host, host->max_clk); - spin_unlock_irq(&host->lock); phy_power_on(sdhci_arasan->phy); - spin_lock_irq(&host->lock); sdhci_arasan->is_phy_on = true; /* @@ -215,18 +213,14 @@ static void sdhci_arasan_set_clock(struct sdhci_host *host, unsigned int clock) } if (ctrl_phy && sdhci_arasan->is_phy_on) { - spin_unlock_irq(&host->lock); phy_power_off(sdhci_arasan->phy); - spin_lock_irq(&host->lock); sdhci_arasan->is_phy_on = false; } sdhci_set_clock(host, clock); if (ctrl_phy) { - spin_unlock_irq(&host->lock); phy_power_on(sdhci_arasan->phy); - spin_lock_irq(&host->lock); sdhci_arasan->is_phy_on = true; } } diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c index 3f08b7ebec43..7611fd679f1a 100644 --- a/drivers/mmc/host/sdhci-of-at91.c +++ b/drivers/mmc/host/sdhci-of-at91.c @@ -98,9 +98,7 @@ static void sdhci_at91_set_power(struct sdhci_host *host, unsigned char mode, if (!IS_ERR(host->mmc->supply.vmmc)) { struct mmc_host *mmc = host->mmc; - spin_unlock_irq(&host->lock); mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); - spin_lock_irq(&host->lock); } sdhci_set_power_noreg(host, mode, vdd); } diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 5c620206138e..98ed90cbd9b4 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -484,8 +484,6 @@ static void sdhci_intel_set_power(struct sdhci_host *host, unsigned char mode, if (mode == MMC_POWER_OFF) return; - spin_unlock_irq(&host->lock); - /* * Bus power might not enable after D3 -> D0 transition due to the * present state not yet having propagated. Retry for up to 2ms. @@ -498,8 +496,6 @@ static void sdhci_intel_set_power(struct sdhci_host *host, unsigned char mode, reg |= SDHCI_POWER_ON; sdhci_writeb(host, reg, SDHCI_POWER_CONTROL); } - - spin_lock_irq(&host->lock); } static const struct sdhci_ops sdhci_intel_byt_ops = { diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index 22ed90433d29..f953f35c2624 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -323,11 +323,8 @@ static void pxav3_set_power(struct sdhci_host *host, unsigned char mode, if (host->pwr == 0) vdd = 0; - if (!IS_ERR(mmc->supply.vmmc)) { - spin_unlock_irq(&host->lock); + if (!IS_ERR(mmc->supply.vmmc)) mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); - spin_lock_irq(&host->lock); - } } static const struct sdhci_ops pxav3_sdhci_ops = { diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index d02284da2ec6..7c065a70f92b 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -190,9 +190,7 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, * speed possible with selected clock source and skip the division. */ if (ourhost->no_divider) { - spin_unlock_irq(&ourhost->host->lock); rate = clk_round_rate(clksrc, wanted); - spin_lock_irq(&ourhost->host->lock); return wanted - rate; } @@ -389,9 +387,7 @@ static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) clk &= ~SDHCI_CLOCK_CARD_EN; sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); - spin_unlock_irq(&host->lock); ret = clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock); - spin_lock_irq(&host->lock); if (ret != 0) { dev_err(dev, "%s: failed to set clock rate %uHz\n", mmc_hostname(host->mmc), clock); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index e055bd3f5fe8..5c2b35dd1c45 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1361,9 +1361,7 @@ void sdhci_enable_clk(struct sdhci_host *host, u16 clk) sdhci_dumpregs(host); return; } - spin_unlock_irq(&host->lock); udelay(10); - spin_lock_irq(&host->lock); } clk |= SDHCI_CLOCK_CARD_EN; @@ -1392,9 +1390,7 @@ static void sdhci_set_power_reg(struct sdhci_host *host, unsigned char mode, { struct mmc_host *mmc = host->mmc; - spin_unlock_irq(&host->lock); mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); - spin_lock_irq(&host->lock); if (mode != MMC_POWER_OFF) sdhci_writeb(host, SDHCI_POWER_ON, SDHCI_POWER_CONTROL); @@ -1574,16 +1570,12 @@ EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling); static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct sdhci_host *host = mmc_priv(mmc); - unsigned long flags; u8 ctrl; if (ios->power_mode == MMC_POWER_UNDEFINED) return; - spin_lock_irqsave(&host->lock, flags); - if (host->flags & SDHCI_DEVICE_DEAD) { - spin_unlock_irqrestore(&host->lock, flags); if (!IS_ERR(mmc->supply.vmmc) && ios->power_mode == MMC_POWER_OFF) mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); @@ -1729,7 +1721,6 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) sdhci_do_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); mmiowb(); - spin_unlock_irqrestore(&host->lock, flags); } static int sdhci_get_cd(struct mmc_host *mmc) From 2a85ef25bee6910f4de510c5b574c81e87f38b05 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:38 +0200 Subject: [PATCH 045/148] mmc: sdhci: Reduce spin lock usage in sdhci_execute_tuning Tuning execution is already synchronized with respect to other host operations by upper layers "claiming" the host, which also takes care of runtime pm. There can be no requests in progress. Retain the spin lock usage only for ensuring that sending tuning commands is synchronized with respect to the interrupt handler. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 5c2b35dd1c45..cfb6d3a23aef 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1993,8 +1993,7 @@ static void sdhci_reset_tuning(struct sdhci_host *host) sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); } -static void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode, - unsigned long flags) +static void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode) { sdhci_reset_tuning(host); @@ -2003,9 +2002,7 @@ static void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode, sdhci_end_tuning(host); - spin_unlock_irqrestore(&host->lock, flags); mmc_abort_tuning(host->mmc, opcode); - spin_lock_irqsave(&host->lock, flags); } /* @@ -2015,12 +2012,14 @@ static void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode, * interrupt setup is different to other commands and there is no timeout * interrupt so special handling is needed. */ -static void sdhci_send_tuning(struct sdhci_host *host, u32 opcode, - unsigned long flags) +static void sdhci_send_tuning(struct sdhci_host *host, u32 opcode) { struct mmc_host *mmc = host->mmc; struct mmc_command cmd = {}; struct mmc_request mrq = {}; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); cmd.opcode = opcode; cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; @@ -2054,17 +2053,16 @@ static void sdhci_send_tuning(struct sdhci_host *host, u32 opcode, host->tuning_done = 0; + mmiowb(); spin_unlock_irqrestore(&host->lock, flags); /* Wait for Buffer Read Ready interrupt */ wait_event_timeout(host->buf_ready_int, (host->tuning_done == 1), msecs_to_jiffies(50)); - spin_lock_irqsave(&host->lock, flags); } -static void __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode, - unsigned long flags) +static void __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode) { int i; @@ -2075,12 +2073,12 @@ static void __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode, for (i = 0; i < MAX_TUNING_LOOP; i++) { u16 ctrl; - sdhci_send_tuning(host, opcode, flags); + sdhci_send_tuning(host, opcode); if (!host->tuning_done) { pr_info("%s: Tuning timeout, falling back to fixed sampling clock\n", mmc_hostname(host->mmc)); - sdhci_abort_tuning(host, opcode, flags); + sdhci_abort_tuning(host, opcode); return; } @@ -2105,12 +2103,9 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) { struct sdhci_host *host = mmc_priv(mmc); int err = 0; - unsigned long flags; unsigned int tuning_count = 0; bool hs400_tuning; - spin_lock_irqsave(&host->lock, flags); - hs400_tuning = host->flags & SDHCI_HS400_TUNING; if (host->tuning_mode == SDHCI_TUNING_MODE_1) @@ -2127,7 +2122,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) /* HS400 tuning is done in HS200 mode */ case MMC_TIMING_MMC_HS400: err = -EINVAL; - goto out_unlock; + goto out; case MMC_TIMING_MMC_HS200: /* @@ -2148,26 +2143,23 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) /* FALLTHROUGH */ default: - goto out_unlock; + goto out; } if (host->ops->platform_execute_tuning) { - spin_unlock_irqrestore(&host->lock, flags); err = host->ops->platform_execute_tuning(host, opcode); - spin_lock_irqsave(&host->lock, flags); - goto out_unlock; + goto out; } host->mmc->retune_period = tuning_count; sdhci_start_tuning(host); - __sdhci_execute_tuning(host, opcode, flags); + __sdhci_execute_tuning(host, opcode); sdhci_end_tuning(host); -out_unlock: +out: host->flags &= ~SDHCI_HS400_TUNING; - spin_unlock_irqrestore(&host->lock, flags); return err; } From f421865d5b4ce57013040fb1700edceb43a14b42 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:39 +0200 Subject: [PATCH 046/148] mmc: sdhci: Improve debug print format Ensure all debug prints start with the mmc host name. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index cfb6d3a23aef..3b79247ca877 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -38,7 +38,7 @@ #define DRIVER_NAME "sdhci" #define DBG(f, x...) \ - pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x) + pr_debug("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x) #define MAX_TUNING_LOOP 40 @@ -715,8 +715,8 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) } if (count >= 0xF) { - DBG("%s: Too large timeout 0x%x requested for CMD%d!\n", - mmc_hostname(host->mmc), count, cmd->opcode); + DBG("Too large timeout 0x%x requested for CMD%d!\n", + count, cmd->opcode); count = 0xE; } @@ -2485,7 +2485,6 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) #ifdef CONFIG_MMC_DEBUG static void sdhci_adma_show_error(struct sdhci_host *host) { - const char *name = mmc_hostname(host->mmc); void *desc = host->adma_table; sdhci_dumpregs(host); @@ -2494,14 +2493,14 @@ static void sdhci_adma_show_error(struct sdhci_host *host) struct sdhci_adma2_64_desc *dma_desc = desc; if (host->flags & SDHCI_USE_64_BIT_DMA) - DBG("%s: %p: DMA 0x%08x%08x, LEN 0x%04x, Attr=0x%02x\n", - name, desc, le32_to_cpu(dma_desc->addr_hi), + DBG("%p: DMA 0x%08x%08x, LEN 0x%04x, Attr=0x%02x\n", + desc, le32_to_cpu(dma_desc->addr_hi), le32_to_cpu(dma_desc->addr_lo), le16_to_cpu(dma_desc->len), le16_to_cpu(dma_desc->cmd)); else - DBG("%s: %p: DMA 0x%08x, LEN 0x%04x, Attr=0x%02x\n", - name, desc, le32_to_cpu(dma_desc->addr_lo), + DBG("%p: DMA 0x%08x, LEN 0x%04x, Attr=0x%02x\n", + desc, le32_to_cpu(dma_desc->addr_lo), le16_to_cpu(dma_desc->len), le16_to_cpu(dma_desc->cmd)); @@ -2617,10 +2616,8 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) + SDHCI_DEFAULT_BOUNDARY_SIZE; host->data->bytes_xfered = dmanow - dmastart; - DBG("%s: DMA base 0x%08x, transferred 0x%06x bytes," - " next 0x%08x\n", - mmc_hostname(host->mmc), dmastart, - host->data->bytes_xfered, dmanow); + DBG("DMA base 0x%08x, transferred 0x%06x bytes, next 0x%08x\n", + dmastart, host->data->bytes_xfered, dmanow); sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS); } @@ -2665,8 +2662,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) SDHCI_INT_BUS_POWER); sdhci_writel(host, mask, SDHCI_INT_STATUS); - DBG("*** %s got interrupt: 0x%08x\n", - mmc_hostname(host->mmc), intmask); + DBG("IRQ status 0x%08x\n", intmask); if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & @@ -3296,9 +3292,9 @@ int sdhci_setup_host(struct sdhci_host *host) !(host->flags & SDHCI_USE_SDMA)) && !(host->quirks2 & SDHCI_QUIRK2_ACMD23_BROKEN)) { host->flags |= SDHCI_AUTO_CMD23; - DBG("%s: Auto-CMD23 available\n", mmc_hostname(mmc)); + DBG("Auto-CMD23 available\n"); } else { - DBG("%s: Auto-CMD23 unavailable\n", mmc_hostname(mmc)); + DBG("Auto-CMD23 unavailable\n"); } /* From 7962302fc3f5b3b31523b518a2dc47849fb7bd06 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:40 +0200 Subject: [PATCH 047/148] mmc: sdhci: Add response register to register dump Add response register to register dump. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 3b79247ca877..49acbd53e3e5 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -87,6 +87,13 @@ static void sdhci_dumpregs(struct sdhci_host *host) pr_err(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", sdhci_readw(host, SDHCI_COMMAND), sdhci_readl(host, SDHCI_MAX_CURRENT)); + pr_err(DRIVER_NAME ": Resp[0]: 0x%08x | Resp[1]: 0x%08x\n", + sdhci_readl(host, SDHCI_RESPONSE), + sdhci_readl(host, SDHCI_RESPONSE + 4)); + pr_err(DRIVER_NAME ": Resp[2]: 0x%08x | Resp[3]: 0x%08x\n", + sdhci_readl(host, SDHCI_RESPONSE + 8), + sdhci_readl(host, SDHCI_RESPONSE + 12)); + pr_err(DRIVER_NAME ": Host ctl2: 0x%08x\n", sdhci_readw(host, SDHCI_HOST_CONTROL2)); From c71024de98f4b596112be9c2698b2d8000382a5a Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:41 +0200 Subject: [PATCH 048/148] mmc: sdhci: Use sdhci_readl() not readl() in sdhci_dumpregs() Use sdhci io wrappers consistently. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 49acbd53e3e5..94005d210d5e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -100,13 +100,13 @@ static void sdhci_dumpregs(struct sdhci_host *host) if (host->flags & SDHCI_USE_ADMA) { if (host->flags & SDHCI_USE_64_BIT_DMA) pr_err(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x%08x\n", - readl(host->ioaddr + SDHCI_ADMA_ERROR), - readl(host->ioaddr + SDHCI_ADMA_ADDRESS_HI), - readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); + sdhci_readl(host, SDHCI_ADMA_ERROR), + sdhci_readl(host, SDHCI_ADMA_ADDRESS_HI), + sdhci_readl(host, SDHCI_ADMA_ADDRESS)); else pr_err(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", - readl(host->ioaddr + SDHCI_ADMA_ERROR), - readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); + sdhci_readl(host, SDHCI_ADMA_ERROR), + sdhci_readl(host, SDHCI_ADMA_ADDRESS)); } pr_err(DRIVER_NAME ": ===========================================\n"); From 85ad90e2f819a5c7f5d1005da32660eff7680abf Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:42 +0200 Subject: [PATCH 049/148] mmc: sdhci: Improve register dump print format Ensure all prints start with the mmc host name, and the text all lines up. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 102 ++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 94005d210d5e..2032bea9bd0d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -40,6 +40,9 @@ #define DBG(f, x...) \ pr_debug("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x) +#define SDHCI_DUMP(f, x...) \ + pr_err("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x) + #define MAX_TUNING_LOOP 40 static unsigned int debug_quirks = 0; @@ -51,65 +54,64 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable); static void sdhci_dumpregs(struct sdhci_host *host) { - pr_err(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", - mmc_hostname(host->mmc)); + SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n"); - pr_err(DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", - sdhci_readl(host, SDHCI_DMA_ADDRESS), - sdhci_readw(host, SDHCI_HOST_VERSION)); - pr_err(DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", - sdhci_readw(host, SDHCI_BLOCK_SIZE), - sdhci_readw(host, SDHCI_BLOCK_COUNT)); - pr_err(DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", - sdhci_readl(host, SDHCI_ARGUMENT), - sdhci_readw(host, SDHCI_TRANSFER_MODE)); - pr_err(DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", - sdhci_readl(host, SDHCI_PRESENT_STATE), - sdhci_readb(host, SDHCI_HOST_CONTROL)); - pr_err(DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", - sdhci_readb(host, SDHCI_POWER_CONTROL), - sdhci_readb(host, SDHCI_BLOCK_GAP_CONTROL)); - pr_err(DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", - sdhci_readb(host, SDHCI_WAKE_UP_CONTROL), - sdhci_readw(host, SDHCI_CLOCK_CONTROL)); - pr_err(DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", - sdhci_readb(host, SDHCI_TIMEOUT_CONTROL), - sdhci_readl(host, SDHCI_INT_STATUS)); - pr_err(DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", - sdhci_readl(host, SDHCI_INT_ENABLE), - sdhci_readl(host, SDHCI_SIGNAL_ENABLE)); - pr_err(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", - sdhci_readw(host, SDHCI_ACMD12_ERR), - sdhci_readw(host, SDHCI_SLOT_INT_STATUS)); - pr_err(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", - sdhci_readl(host, SDHCI_CAPABILITIES), - sdhci_readl(host, SDHCI_CAPABILITIES_1)); - pr_err(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", - sdhci_readw(host, SDHCI_COMMAND), - sdhci_readl(host, SDHCI_MAX_CURRENT)); - pr_err(DRIVER_NAME ": Resp[0]: 0x%08x | Resp[1]: 0x%08x\n", + SDHCI_DUMP("Sys addr: 0x%08x | Version: 0x%08x\n", + sdhci_readl(host, SDHCI_DMA_ADDRESS), + sdhci_readw(host, SDHCI_HOST_VERSION)); + SDHCI_DUMP("Blk size: 0x%08x | Blk cnt: 0x%08x\n", + sdhci_readw(host, SDHCI_BLOCK_SIZE), + sdhci_readw(host, SDHCI_BLOCK_COUNT)); + SDHCI_DUMP("Argument: 0x%08x | Trn mode: 0x%08x\n", + sdhci_readl(host, SDHCI_ARGUMENT), + sdhci_readw(host, SDHCI_TRANSFER_MODE)); + SDHCI_DUMP("Present: 0x%08x | Host ctl: 0x%08x\n", + sdhci_readl(host, SDHCI_PRESENT_STATE), + sdhci_readb(host, SDHCI_HOST_CONTROL)); + SDHCI_DUMP("Power: 0x%08x | Blk gap: 0x%08x\n", + sdhci_readb(host, SDHCI_POWER_CONTROL), + sdhci_readb(host, SDHCI_BLOCK_GAP_CONTROL)); + SDHCI_DUMP("Wake-up: 0x%08x | Clock: 0x%08x\n", + sdhci_readb(host, SDHCI_WAKE_UP_CONTROL), + sdhci_readw(host, SDHCI_CLOCK_CONTROL)); + SDHCI_DUMP("Timeout: 0x%08x | Int stat: 0x%08x\n", + sdhci_readb(host, SDHCI_TIMEOUT_CONTROL), + sdhci_readl(host, SDHCI_INT_STATUS)); + SDHCI_DUMP("Int enab: 0x%08x | Sig enab: 0x%08x\n", + sdhci_readl(host, SDHCI_INT_ENABLE), + sdhci_readl(host, SDHCI_SIGNAL_ENABLE)); + SDHCI_DUMP("AC12 err: 0x%08x | Slot int: 0x%08x\n", + sdhci_readw(host, SDHCI_ACMD12_ERR), + sdhci_readw(host, SDHCI_SLOT_INT_STATUS)); + SDHCI_DUMP("Caps: 0x%08x | Caps_1: 0x%08x\n", + sdhci_readl(host, SDHCI_CAPABILITIES), + sdhci_readl(host, SDHCI_CAPABILITIES_1)); + SDHCI_DUMP("Cmd: 0x%08x | Max curr: 0x%08x\n", + sdhci_readw(host, SDHCI_COMMAND), + sdhci_readl(host, SDHCI_MAX_CURRENT)); + SDHCI_DUMP("Resp[0]: 0x%08x | Resp[1]: 0x%08x\n", sdhci_readl(host, SDHCI_RESPONSE), sdhci_readl(host, SDHCI_RESPONSE + 4)); - pr_err(DRIVER_NAME ": Resp[2]: 0x%08x | Resp[3]: 0x%08x\n", + SDHCI_DUMP("Resp[2]: 0x%08x | Resp[3]: 0x%08x\n", sdhci_readl(host, SDHCI_RESPONSE + 8), sdhci_readl(host, SDHCI_RESPONSE + 12)); - - pr_err(DRIVER_NAME ": Host ctl2: 0x%08x\n", - sdhci_readw(host, SDHCI_HOST_CONTROL2)); + SDHCI_DUMP("Host ctl2: 0x%08x\n", + sdhci_readw(host, SDHCI_HOST_CONTROL2)); if (host->flags & SDHCI_USE_ADMA) { - if (host->flags & SDHCI_USE_64_BIT_DMA) - pr_err(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x%08x\n", - sdhci_readl(host, SDHCI_ADMA_ERROR), - sdhci_readl(host, SDHCI_ADMA_ADDRESS_HI), - sdhci_readl(host, SDHCI_ADMA_ADDRESS)); - else - pr_err(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", - sdhci_readl(host, SDHCI_ADMA_ERROR), - sdhci_readl(host, SDHCI_ADMA_ADDRESS)); + if (host->flags & SDHCI_USE_64_BIT_DMA) { + SDHCI_DUMP("ADMA Err: 0x%08x | ADMA Ptr: 0x%08x%08x\n", + sdhci_readl(host, SDHCI_ADMA_ERROR), + sdhci_readl(host, SDHCI_ADMA_ADDRESS_HI), + sdhci_readl(host, SDHCI_ADMA_ADDRESS)); + } else { + SDHCI_DUMP("ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", + sdhci_readl(host, SDHCI_ADMA_ERROR), + sdhci_readl(host, SDHCI_ADMA_ADDRESS)); + } } - pr_err(DRIVER_NAME ": ===========================================\n"); + SDHCI_DUMP("============================================\n"); } /*****************************************************************************\ From d28981727dccd0e1e867deceaa709bc89f1a5ebc Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:43 +0200 Subject: [PATCH 050/148] mmc: sdhci: Export sdhci_dumpregs Export sdhci_dumpregs so that it can be called by drivers. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 3 ++- drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2032bea9bd0d..7cb25dc1984f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -52,7 +52,7 @@ static void sdhci_finish_data(struct sdhci_host *); static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable); -static void sdhci_dumpregs(struct sdhci_host *host) +void sdhci_dumpregs(struct sdhci_host *host) { SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n"); @@ -113,6 +113,7 @@ static void sdhci_dumpregs(struct sdhci_host *host) SDHCI_DUMP("============================================\n"); } +EXPORT_SYMBOL_GPL(sdhci_dumpregs); /*****************************************************************************\ * * diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 67772674da90..51c440eebe5c 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -698,4 +698,6 @@ extern int sdhci_runtime_suspend_host(struct sdhci_host *host); extern int sdhci_runtime_resume_host(struct sdhci_host *host); #endif +void sdhci_dumpregs(struct sdhci_host *host); + #endif /* __SDHCI_HW_H */ From 15becf68b86741be282880196f1e5155b1a9a033 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:44 +0200 Subject: [PATCH 051/148] mmc: sdhci: Get rid of 'extern' in header file Get rid of unnecessary 'extern' in header file. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.h | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 51c440eebe5c..6fe6005b284e 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -648,24 +648,22 @@ static inline u8 sdhci_readb(struct sdhci_host *host, int reg) #endif /* CONFIG_MMC_SDHCI_IO_ACCESSORS */ -extern struct sdhci_host *sdhci_alloc_host(struct device *dev, - size_t priv_size); -extern void sdhci_free_host(struct sdhci_host *host); +struct sdhci_host *sdhci_alloc_host(struct device *dev, size_t priv_size); +void sdhci_free_host(struct sdhci_host *host); static inline void *sdhci_priv(struct sdhci_host *host) { return host->private; } -extern void sdhci_card_detect(struct sdhci_host *host); -extern void __sdhci_read_caps(struct sdhci_host *host, u16 *ver, u32 *caps, - u32 *caps1); -extern int sdhci_setup_host(struct sdhci_host *host); -extern int __sdhci_add_host(struct sdhci_host *host); -extern int sdhci_add_host(struct sdhci_host *host); -extern void sdhci_remove_host(struct sdhci_host *host, int dead); -extern void sdhci_send_command(struct sdhci_host *host, - struct mmc_command *cmd); +void sdhci_card_detect(struct sdhci_host *host); +void __sdhci_read_caps(struct sdhci_host *host, u16 *ver, u32 *caps, + u32 *caps1); +int sdhci_setup_host(struct sdhci_host *host); +int __sdhci_add_host(struct sdhci_host *host); +int sdhci_add_host(struct sdhci_host *host); +void sdhci_remove_host(struct sdhci_host *host, int dead); +void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd); static inline void sdhci_read_caps(struct sdhci_host *host) { @@ -691,11 +689,11 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing); int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode); #ifdef CONFIG_PM -extern int sdhci_suspend_host(struct sdhci_host *host); -extern int sdhci_resume_host(struct sdhci_host *host); -extern void sdhci_enable_irq_wakeups(struct sdhci_host *host); -extern int sdhci_runtime_suspend_host(struct sdhci_host *host); -extern int sdhci_runtime_resume_host(struct sdhci_host *host); +int sdhci_suspend_host(struct sdhci_host *host); +int sdhci_resume_host(struct sdhci_host *host); +void sdhci_enable_irq_wakeups(struct sdhci_host *host); +int sdhci_runtime_suspend_host(struct sdhci_host *host); +int sdhci_runtime_resume_host(struct sdhci_host *host); #endif void sdhci_dumpregs(struct sdhci_host *host); From 4180ffa81779551a25772efae1128ac5a9689536 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:45 +0200 Subject: [PATCH 052/148] mmc: sdhci: Add sdhci_cleanup_host Add sdhci_cleanup_host() to cleanup __sdhci_add_host(). Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 37 ++++++++++++++++++++++++++----------- drivers/mmc/host/sdhci.h | 1 + 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 7cb25dc1984f..48631b812688 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3568,6 +3568,22 @@ undma: } EXPORT_SYMBOL_GPL(sdhci_setup_host); +void sdhci_cleanup_host(struct sdhci_host *host) +{ + struct mmc_host *mmc = host->mmc; + + if (!IS_ERR(mmc->supply.vqmmc)) + regulator_disable(mmc->supply.vqmmc); + + if (host->align_buffer) + dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + + host->adma_table_sz, host->align_buffer, + host->align_addr); + host->adma_table = NULL; + host->align_buffer = NULL; +} +EXPORT_SYMBOL_GPL(sdhci_cleanup_host); + int __sdhci_add_host(struct sdhci_host *host) { struct mmc_host *mmc = host->mmc; @@ -3632,16 +3648,6 @@ unirq: untasklet: tasklet_kill(&host->finish_tasklet); - if (!IS_ERR(mmc->supply.vqmmc)) - regulator_disable(mmc->supply.vqmmc); - - if (host->align_buffer) - dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + - host->adma_table_sz, host->align_buffer, - host->align_addr); - host->adma_table = NULL; - host->align_buffer = NULL; - return ret; } EXPORT_SYMBOL_GPL(__sdhci_add_host); @@ -3654,7 +3660,16 @@ int sdhci_add_host(struct sdhci_host *host) if (ret) return ret; - return __sdhci_add_host(host); + ret = __sdhci_add_host(host); + if (ret) + goto cleanup; + + return 0; + +cleanup: + sdhci_cleanup_host(host); + + return ret; } EXPORT_SYMBOL_GPL(sdhci_add_host); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 6fe6005b284e..477b3f5bedfd 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -660,6 +660,7 @@ void sdhci_card_detect(struct sdhci_host *host); void __sdhci_read_caps(struct sdhci_host *host, u16 *ver, u32 *caps, u32 *caps1); int sdhci_setup_host(struct sdhci_host *host); +void sdhci_cleanup_host(struct sdhci_host *host); int __sdhci_add_host(struct sdhci_host *host); int sdhci_add_host(struct sdhci_host *host); void sdhci_remove_host(struct sdhci_host *host, int dead); From f5c1ab821b96419261e7676efb1a355e9d735faf Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:46 +0200 Subject: [PATCH 053/148] mmc: sdhci: Factor out sdhci_set_default_irqs Factor out sdhci_set_default_irqs(). Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 48631b812688..176e07f7c6ea 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -225,15 +225,8 @@ static void sdhci_do_reset(struct sdhci_host *host, u8 mask) } } -static void sdhci_init(struct sdhci_host *host, int soft) +static void sdhci_set_default_irqs(struct sdhci_host *host) { - struct mmc_host *mmc = host->mmc; - - if (soft) - sdhci_do_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA); - else - sdhci_do_reset(host, SDHCI_RESET_ALL); - host->ier = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC | @@ -246,6 +239,18 @@ static void sdhci_init(struct sdhci_host *host, int soft) sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); +} + +static void sdhci_init(struct sdhci_host *host, int soft) +{ + struct mmc_host *mmc = host->mmc; + + if (soft) + sdhci_do_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + else + sdhci_do_reset(host, SDHCI_RESET_ALL); + + sdhci_set_default_irqs(host); if (soft) { /* force clock reconfiguration */ From f12e39dbf3e6eb521435085712addc131d803bfd Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:47 +0200 Subject: [PATCH 054/148] mmc: sdhci: Add CQE support Add an interrupt hook and helper functions for enabling, disabling and delivering interrupts to a CQE. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci.c | 130 ++++++++++++++++++++++++++++++++++++++- drivers/mmc/host/sdhci.h | 19 ++++++ 2 files changed, 146 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 176e07f7c6ea..a971fcfa6de7 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -252,6 +252,8 @@ static void sdhci_init(struct sdhci_host *host, int soft) sdhci_set_default_irqs(host); + host->cqe_on = false; + if (soft) { /* force clock reconfiguration */ host->clock = 0; @@ -2672,13 +2674,19 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) } do { + DBG("IRQ status 0x%08x\n", intmask); + + if (host->ops->irq) { + intmask = host->ops->irq(host, intmask); + if (!intmask) + goto cont; + } + /* Clear selected interrupts. */ mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | SDHCI_INT_BUS_POWER); sdhci_writel(host, mask, SDHCI_INT_STATUS); - DBG("IRQ status 0x%08x\n", intmask); - if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT; @@ -2738,7 +2746,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) unexpected |= intmask; sdhci_writel(host, intmask, SDHCI_INT_STATUS); } - +cont: if (result == IRQ_NONE) result = IRQ_HANDLED; @@ -2965,6 +2973,119 @@ EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host); #endif /* CONFIG_PM */ +/*****************************************************************************\ + * * + * Command Queue Engine (CQE) helpers * + * * +\*****************************************************************************/ + +void sdhci_cqe_enable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + unsigned long flags; + u8 ctrl; + + spin_lock_irqsave(&host->lock, flags); + + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl &= ~SDHCI_CTRL_DMA_MASK; + if (host->flags & SDHCI_USE_64_BIT_DMA) + ctrl |= SDHCI_CTRL_ADMA64; + else + ctrl |= SDHCI_CTRL_ADMA32; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + + sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 512), + SDHCI_BLOCK_SIZE); + + /* Set maximum timeout */ + sdhci_writeb(host, 0xE, SDHCI_TIMEOUT_CONTROL); + + host->ier = host->cqe_ier; + + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + + host->cqe_on = true; + + pr_debug("%s: sdhci: CQE on, IRQ mask %#x, IRQ status %#x\n", + mmc_hostname(mmc), host->ier, + sdhci_readl(host, SDHCI_INT_STATUS)); + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} +EXPORT_SYMBOL_GPL(sdhci_cqe_enable); + +void sdhci_cqe_disable(struct mmc_host *mmc, bool recovery) +{ + struct sdhci_host *host = mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + sdhci_set_default_irqs(host); + + host->cqe_on = false; + + if (recovery) { + sdhci_do_reset(host, SDHCI_RESET_CMD); + sdhci_do_reset(host, SDHCI_RESET_DATA); + } + + pr_debug("%s: sdhci: CQE off, IRQ mask %#x, IRQ status %#x\n", + mmc_hostname(mmc), host->ier, + sdhci_readl(host, SDHCI_INT_STATUS)); + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} +EXPORT_SYMBOL_GPL(sdhci_cqe_disable); + +bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error, + int *data_error) +{ + u32 mask; + + if (!host->cqe_on) + return false; + + if (intmask & (SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC)) + *cmd_error = -EILSEQ; + else if (intmask & SDHCI_INT_TIMEOUT) + *cmd_error = -ETIMEDOUT; + else + *cmd_error = 0; + + if (intmask & (SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC)) + *data_error = -EILSEQ; + else if (intmask & SDHCI_INT_DATA_TIMEOUT) + *data_error = -ETIMEDOUT; + else if (intmask & SDHCI_INT_ADMA_ERROR) + *data_error = -EIO; + else + *data_error = 0; + + /* Clear selected interrupts. */ + mask = intmask & host->cqe_ier; + sdhci_writel(host, mask, SDHCI_INT_STATUS); + + if (intmask & SDHCI_INT_BUS_POWER) + pr_err("%s: Card is consuming too much power!\n", + mmc_hostname(host->mmc)); + + intmask &= ~(host->cqe_ier | SDHCI_INT_ERROR); + if (intmask) { + sdhci_writel(host, intmask, SDHCI_INT_STATUS); + pr_err("%s: CQE: Unexpected interrupt 0x%08x.\n", + mmc_hostname(host->mmc), intmask); + sdhci_dumpregs(host); + } + + return true; +} +EXPORT_SYMBOL_GPL(sdhci_cqe_irq); + /*****************************************************************************\ * * * Device allocation/registration * @@ -2990,6 +3111,9 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, host->flags = SDHCI_SIGNALING_330; + host->cqe_ier = SDHCI_CQE_INT_MASK; + host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK; + return host; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 477b3f5bedfd..35b41da0a636 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -134,6 +134,7 @@ #define SDHCI_INT_CARD_REMOVE 0x00000080 #define SDHCI_INT_CARD_INT 0x00000100 #define SDHCI_INT_RETUNE 0x00001000 +#define SDHCI_INT_CQE 0x00004000 #define SDHCI_INT_ERROR 0x00008000 #define SDHCI_INT_TIMEOUT 0x00010000 #define SDHCI_INT_CRC 0x00020000 @@ -158,6 +159,13 @@ SDHCI_INT_BLK_GAP) #define SDHCI_INT_ALL_MASK ((unsigned int)-1) +#define SDHCI_CQE_INT_ERR_MASK ( \ + SDHCI_INT_ADMA_ERROR | SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | \ + SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | \ + SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT) + +#define SDHCI_CQE_INT_MASK (SDHCI_CQE_INT_ERR_MASK | SDHCI_INT_CQE) + #define SDHCI_ACMD12_ERR 0x3C #define SDHCI_HOST_CONTROL2 0x3E @@ -518,6 +526,10 @@ struct sdhci_host { /* cached registers */ u32 ier; + bool cqe_on; /* CQE is operating */ + u32 cqe_ier; /* CQE interrupt mask */ + u32 cqe_err_ier; /* CQE error interrupt mask */ + wait_queue_head_t buf_ready_int; /* Waitqueue for Buffer Read Ready interrupt */ unsigned int tuning_done; /* Condition flag set when CMD19 succeeds */ @@ -544,6 +556,8 @@ struct sdhci_ops { void (*set_power)(struct sdhci_host *host, unsigned char mode, unsigned short vdd); + u32 (*irq)(struct sdhci_host *host, u32 intmask); + int (*enable_dma)(struct sdhci_host *host); unsigned int (*get_max_clock)(struct sdhci_host *host); unsigned int (*get_min_clock)(struct sdhci_host *host); @@ -697,6 +711,11 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host); int sdhci_runtime_resume_host(struct sdhci_host *host); #endif +void sdhci_cqe_enable(struct mmc_host *mmc); +void sdhci_cqe_disable(struct mmc_host *mmc, bool recovery); +bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error, + int *data_error); + void sdhci_dumpregs(struct sdhci_host *host); #endif /* __SDHCI_HW_H */ From 61c951dedce6aa4dbc5456730233c30ac0d30d5d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:48 +0200 Subject: [PATCH 055/148] mmc: sdhci-pci: Let devices define how to add the host SDHCI provides more flexibility than simply calling sdhci_add_host(). Make that available by allowing devices to specify their own ->add_host() function. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 5 ++++- drivers/mmc/host/sdhci-pci.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 98ed90cbd9b4..b5fd82e26125 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -1949,7 +1949,10 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( } } - ret = sdhci_add_host(host); + if (chip->fixes && chip->fixes->add_host) + ret = chip->fixes->add_host(slot); + else + ret = sdhci_add_host(host); if (ret) goto remove; diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index da7b95016c6f..d53dccb0f416 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -64,6 +64,7 @@ struct sdhci_pci_fixes { int (*probe) (struct sdhci_pci_chip *); int (*probe_slot) (struct sdhci_pci_slot *); + int (*add_host) (struct sdhci_pci_slot *); void (*remove_slot) (struct sdhci_pci_slot *, int); int (*suspend) (struct sdhci_pci_chip *); From 1cb0a58ec70402bab49bdb4c5d152e82fd82200e Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:49 +0200 Subject: [PATCH 056/148] mmc: sdhci-pci: Do not use suspend/resume callbacks with runtime pm Do not use suspend/resume callbacks with runtime pm. It doesn't make sense and isn't being used, so remove. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index b5fd82e26125..7e8163affb26 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -1783,12 +1783,6 @@ static int sdhci_pci_runtime_suspend(struct device *dev) mmc_retune_needed(host->mmc); } - if (chip->fixes && chip->fixes->suspend) { - ret = chip->fixes->suspend(chip); - if (ret) - goto err_pci_runtime_suspend; - } - return 0; err_pci_runtime_suspend: @@ -1808,12 +1802,6 @@ static int sdhci_pci_runtime_resume(struct device *dev) if (!chip) return 0; - if (chip->fixes && chip->fixes->resume) { - ret = chip->fixes->resume(chip); - if (ret) - return ret; - } - for (i = 0; i < chip->num_slots; i++) { slot = chip->slots[i]; if (!slot) From b7813f0fe9535cd9536102b9ac663a669f9be131 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:50 +0200 Subject: [PATCH 057/148] mmc: sdhci-pci: Conditionally compile pm sleep functions It is confusing to have some parts of suspend / resume under conditional compilation and some parts not. Use conditional compilation everywhere. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 10 ++++++++++ drivers/mmc/host/sdhci-pci-o2micro.c | 2 ++ drivers/mmc/host/sdhci-pci.h | 2 ++ 3 files changed, 14 insertions(+) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 7e8163affb26..47a3965dff4d 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -67,6 +67,7 @@ static int ricoh_mmc_probe_slot(struct sdhci_pci_slot *slot) return 0; } +#ifdef CONFIG_PM_SLEEP static int ricoh_mmc_resume(struct sdhci_pci_chip *chip) { /* Apply a delay to allow controller to settle */ @@ -75,6 +76,7 @@ static int ricoh_mmc_resume(struct sdhci_pci_chip *chip) msleep(500); return 0; } +#endif static const struct sdhci_pci_fixes sdhci_ricoh = { .probe = ricoh_probe, @@ -85,7 +87,9 @@ static const struct sdhci_pci_fixes sdhci_ricoh = { static const struct sdhci_pci_fixes sdhci_ricoh_mmc = { .probe_slot = ricoh_mmc_probe_slot, +#ifdef CONFIG_PM_SLEEP .resume = ricoh_mmc_resume, +#endif .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_CLOCK_BEFORE_RESET | SDHCI_QUIRK_NO_CARD_NO_RESET | @@ -751,6 +755,7 @@ static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead) jmicron_enable_mmc(slot->host, 0); } +#ifdef CONFIG_PM_SLEEP static int jmicron_suspend(struct sdhci_pci_chip *chip) { int i; @@ -782,13 +787,16 @@ static int jmicron_resume(struct sdhci_pci_chip *chip) return 0; } +#endif static const struct sdhci_pci_fixes sdhci_o2 = { .probe = sdhci_pci_o2_probe, .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, .quirks2 = SDHCI_QUIRK2_CLEAR_TRANSFERMODE_REG_BEFORE_CMD, .probe_slot = sdhci_pci_o2_probe_slot, +#ifdef CONFIG_PM_SLEEP .resume = sdhci_pci_o2_resume, +#endif }; static const struct sdhci_pci_fixes sdhci_jmicron = { @@ -797,8 +805,10 @@ static const struct sdhci_pci_fixes sdhci_jmicron = { .probe_slot = jmicron_probe_slot, .remove_slot = jmicron_remove_slot, +#ifdef CONFIG_PM_SLEEP .suspend = jmicron_suspend, .resume = jmicron_resume, +#endif }; /* SysKonnect CardBus2SDIO extra registers */ diff --git a/drivers/mmc/host/sdhci-pci-o2micro.c b/drivers/mmc/host/sdhci-pci-o2micro.c index d48f03104b5b..0ea34e2c7cba 100644 --- a/drivers/mmc/host/sdhci-pci-o2micro.c +++ b/drivers/mmc/host/sdhci-pci-o2micro.c @@ -384,8 +384,10 @@ int sdhci_pci_o2_probe(struct sdhci_pci_chip *chip) return 0; } +#ifdef CONFIG_PM_SLEEP int sdhci_pci_o2_resume(struct sdhci_pci_chip *chip) { sdhci_pci_o2_probe(chip); return 0; } +#endif diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index d53dccb0f416..e70a27058e0b 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -67,8 +67,10 @@ struct sdhci_pci_fixes { int (*add_host) (struct sdhci_pci_slot *); void (*remove_slot) (struct sdhci_pci_slot *, int); +#ifdef CONFIG_PM_SLEEP int (*suspend) (struct sdhci_pci_chip *); int (*resume) (struct sdhci_pci_chip *); +#endif const struct sdhci_ops *ops; size_t priv_size; From 30cf2803b268f2bcce91fbaa19cee6d04b14bd86 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:51 +0200 Subject: [PATCH 058/148] mmc: sdhci-pci: Let suspend/resume callbacks replace default callbacks The suspend / resume callbacks lack the flexibility to allow a device to specify a different function entirely. Change them around so that device functions are called directly and they in turn can call the default implementations if needed. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 170 ++++++++++++++++----------- drivers/mmc/host/sdhci-pci-o2micro.c | 2 +- drivers/mmc/host/sdhci-pci.h | 4 + 3 files changed, 104 insertions(+), 72 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 47a3965dff4d..5ed8369704fd 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -37,6 +37,88 @@ static int sdhci_pci_enable_dma(struct sdhci_host *host); static void sdhci_pci_set_bus_width(struct sdhci_host *host, int width); static void sdhci_pci_hw_reset(struct sdhci_host *host); +#ifdef CONFIG_PM_SLEEP +static int __sdhci_pci_suspend_host(struct sdhci_pci_chip *chip) +{ + int i, ret; + + for (i = 0; i < chip->num_slots; i++) { + struct sdhci_pci_slot *slot = chip->slots[i]; + struct sdhci_host *host; + + if (!slot) + continue; + + host = slot->host; + + if (chip->pm_retune && host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + + ret = sdhci_suspend_host(host); + if (ret) + goto err_pci_suspend; + + if (host->mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ) + sdhci_enable_irq_wakeups(host); + } + + return 0; + +err_pci_suspend: + while (--i >= 0) + sdhci_resume_host(chip->slots[i]->host); + return ret; +} + +static int sdhci_pci_init_wakeup(struct sdhci_pci_chip *chip) +{ + mmc_pm_flag_t pm_flags = 0; + int i; + + for (i = 0; i < chip->num_slots; i++) { + struct sdhci_pci_slot *slot = chip->slots[i]; + + if (slot) + pm_flags |= slot->host->mmc->pm_flags; + } + + return device_init_wakeup(&chip->pdev->dev, + (pm_flags & MMC_PM_KEEP_POWER) && + (pm_flags & MMC_PM_WAKE_SDIO_IRQ)); +} + +static int sdhci_pci_suspend_host(struct sdhci_pci_chip *chip) +{ + int ret; + + ret = __sdhci_pci_suspend_host(chip); + if (ret) + return ret; + + sdhci_pci_init_wakeup(chip); + + return 0; +} + +int sdhci_pci_resume_host(struct sdhci_pci_chip *chip) +{ + struct sdhci_pci_slot *slot; + int i, ret; + + for (i = 0; i < chip->num_slots; i++) { + slot = chip->slots[i]; + if (!slot) + continue; + + ret = sdhci_resume_host(slot->host); + if (ret) + return ret; + } + + return 0; +} +#endif + /*****************************************************************************\ * * * Hardware specific quirk handling * @@ -74,7 +156,7 @@ static int ricoh_mmc_resume(struct sdhci_pci_chip *chip) /* Otherwise it becomes confused if card state changed during suspend */ msleep(500); - return 0; + return sdhci_pci_resume_host(chip); } #endif @@ -758,7 +840,11 @@ static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead) #ifdef CONFIG_PM_SLEEP static int jmicron_suspend(struct sdhci_pci_chip *chip) { - int i; + int i, ret; + + ret = __sdhci_pci_suspend_host(chip); + if (ret) + return ret; if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC || chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) { @@ -766,6 +852,8 @@ static int jmicron_suspend(struct sdhci_pci_chip *chip) jmicron_enable_mmc(chip->slots[i]->host, 0); } + sdhci_pci_init_wakeup(chip); + return 0; } @@ -785,7 +873,7 @@ static int jmicron_resume(struct sdhci_pci_chip *chip) return ret; } - return 0; + return sdhci_pci_resume_host(chip); } #endif @@ -1678,89 +1766,29 @@ static const struct sdhci_ops sdhci_pci_ops = { static int sdhci_pci_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); - struct sdhci_pci_chip *chip; - struct sdhci_pci_slot *slot; - struct sdhci_host *host; - mmc_pm_flag_t slot_pm_flags; - mmc_pm_flag_t pm_flags = 0; - int i, ret; + struct sdhci_pci_chip *chip = pci_get_drvdata(pdev); - chip = pci_get_drvdata(pdev); if (!chip) return 0; - for (i = 0; i < chip->num_slots; i++) { - slot = chip->slots[i]; - if (!slot) - continue; + if (chip->fixes && chip->fixes->suspend) + return chip->fixes->suspend(chip); - host = slot->host; - - if (chip->pm_retune && host->tuning_mode != SDHCI_TUNING_MODE_3) - mmc_retune_needed(host->mmc); - - ret = sdhci_suspend_host(host); - - if (ret) - goto err_pci_suspend; - - slot_pm_flags = host->mmc->pm_flags; - if (slot_pm_flags & MMC_PM_WAKE_SDIO_IRQ) - sdhci_enable_irq_wakeups(host); - - pm_flags |= slot_pm_flags; - } - - if (chip->fixes && chip->fixes->suspend) { - ret = chip->fixes->suspend(chip); - if (ret) - goto err_pci_suspend; - } - - if (pm_flags & MMC_PM_KEEP_POWER) { - if (pm_flags & MMC_PM_WAKE_SDIO_IRQ) - device_init_wakeup(dev, true); - else - device_init_wakeup(dev, false); - } else - device_init_wakeup(dev, false); - - return 0; - -err_pci_suspend: - while (--i >= 0) - sdhci_resume_host(chip->slots[i]->host); - return ret; + return sdhci_pci_suspend_host(chip); } static int sdhci_pci_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); - struct sdhci_pci_chip *chip; - struct sdhci_pci_slot *slot; - int i, ret; + struct sdhci_pci_chip *chip = pci_get_drvdata(pdev); - chip = pci_get_drvdata(pdev); if (!chip) return 0; - if (chip->fixes && chip->fixes->resume) { - ret = chip->fixes->resume(chip); - if (ret) - return ret; - } + if (chip->fixes && chip->fixes->resume) + return chip->fixes->resume(chip); - for (i = 0; i < chip->num_slots; i++) { - slot = chip->slots[i]; - if (!slot) - continue; - - ret = sdhci_resume_host(slot->host); - if (ret) - return ret; - } - - return 0; + return sdhci_pci_resume_host(chip); } #endif diff --git a/drivers/mmc/host/sdhci-pci-o2micro.c b/drivers/mmc/host/sdhci-pci-o2micro.c index 0ea34e2c7cba..14273ca00641 100644 --- a/drivers/mmc/host/sdhci-pci-o2micro.c +++ b/drivers/mmc/host/sdhci-pci-o2micro.c @@ -388,6 +388,6 @@ int sdhci_pci_o2_probe(struct sdhci_pci_chip *chip) int sdhci_pci_o2_resume(struct sdhci_pci_chip *chip) { sdhci_pci_o2_probe(chip); - return 0; + return sdhci_pci_resume_host(chip); } #endif diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index e70a27058e0b..ec8f91c403d6 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -111,4 +111,8 @@ static inline void *sdhci_pci_priv(struct sdhci_pci_slot *slot) return (void *)slot->private; } +#ifdef CONFIG_PM_SLEEP +int sdhci_pci_resume_host(struct sdhci_pci_chip *chip); +#endif + #endif /* __SDHCI_PCI_H */ From 966d696a622b47663ae866df08d54a4a1d3ec39c Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:52 +0200 Subject: [PATCH 059/148] mmc: sdhci-pci: Add runtime suspend/resume callbacks Add runtime suspend/resume callbacks to match suspend/resume callbacks. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 98 ++++++++++++++++++------------- drivers/mmc/host/sdhci-pci.h | 4 ++ 2 files changed, 62 insertions(+), 40 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 5ed8369704fd..d9a38c53e2ba 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -119,6 +119,56 @@ int sdhci_pci_resume_host(struct sdhci_pci_chip *chip) } #endif +#ifdef CONFIG_PM +static int sdhci_pci_runtime_suspend_host(struct sdhci_pci_chip *chip) +{ + struct sdhci_pci_slot *slot; + struct sdhci_host *host; + int i, ret; + + for (i = 0; i < chip->num_slots; i++) { + slot = chip->slots[i]; + if (!slot) + continue; + + host = slot->host; + + ret = sdhci_runtime_suspend_host(host); + if (ret) + goto err_pci_runtime_suspend; + + if (chip->rpm_retune && + host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + } + + return 0; + +err_pci_runtime_suspend: + while (--i >= 0) + sdhci_runtime_resume_host(chip->slots[i]->host); + return ret; +} + +static int sdhci_pci_runtime_resume_host(struct sdhci_pci_chip *chip) +{ + struct sdhci_pci_slot *slot; + int i, ret; + + for (i = 0; i < chip->num_slots; i++) { + slot = chip->slots[i]; + if (!slot) + continue; + + ret = sdhci_runtime_resume_host(slot->host); + if (ret) + return ret; + } + + return 0; +} +#endif + /*****************************************************************************\ * * * Hardware specific quirk handling * @@ -1796,61 +1846,29 @@ static int sdhci_pci_resume(struct device *dev) static int sdhci_pci_runtime_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); - struct sdhci_pci_chip *chip; - struct sdhci_pci_slot *slot; - struct sdhci_host *host; - int i, ret; + struct sdhci_pci_chip *chip = pci_get_drvdata(pdev); - chip = pci_get_drvdata(pdev); if (!chip) return 0; - for (i = 0; i < chip->num_slots; i++) { - slot = chip->slots[i]; - if (!slot) - continue; + if (chip->fixes && chip->fixes->runtime_suspend) + return chip->fixes->runtime_suspend(chip); - host = slot->host; - - ret = sdhci_runtime_suspend_host(host); - if (ret) - goto err_pci_runtime_suspend; - - if (chip->rpm_retune && - host->tuning_mode != SDHCI_TUNING_MODE_3) - mmc_retune_needed(host->mmc); - } - - return 0; - -err_pci_runtime_suspend: - while (--i >= 0) - sdhci_runtime_resume_host(chip->slots[i]->host); - return ret; + return sdhci_pci_runtime_suspend_host(chip); } static int sdhci_pci_runtime_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); - struct sdhci_pci_chip *chip; - struct sdhci_pci_slot *slot; - int i, ret; + struct sdhci_pci_chip *chip = pci_get_drvdata(pdev); - chip = pci_get_drvdata(pdev); if (!chip) return 0; - for (i = 0; i < chip->num_slots; i++) { - slot = chip->slots[i]; - if (!slot) - continue; + if (chip->fixes && chip->fixes->runtime_resume) + return chip->fixes->runtime_resume(chip); - ret = sdhci_runtime_resume_host(slot->host); - if (ret) - return ret; - } - - return 0; + return sdhci_pci_runtime_resume_host(chip); } #endif diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index ec8f91c403d6..37766d20a600 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -71,6 +71,10 @@ struct sdhci_pci_fixes { int (*suspend) (struct sdhci_pci_chip *); int (*resume) (struct sdhci_pci_chip *); #endif +#ifdef CONFIG_PM + int (*runtime_suspend) (struct sdhci_pci_chip *); + int (*runtime_resume) (struct sdhci_pci_chip *); +#endif const struct sdhci_ops *ops; size_t priv_size; From 48d685a2ec2bc5852cf117b558dce9e90da36601 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 20 Mar 2017 19:50:53 +0200 Subject: [PATCH 060/148] mmc: sdhci-pci: Move a function to avoid later forward declaration Move a function to avoid having to forward declare it in a subsequent patch. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson Tested-by: Ludovic Desroches --- drivers/mmc/host/sdhci-pci-core.c | 76 +++++++++++++++---------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index d9a38c53e2ba..6c0440ef54be 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -515,6 +515,44 @@ out: return ret; } +#define SDHCI_INTEL_PWR_TIMEOUT_CNT 20 +#define SDHCI_INTEL_PWR_TIMEOUT_UDELAY 100 + +static void sdhci_intel_set_power(struct sdhci_host *host, unsigned char mode, + unsigned short vdd) +{ + int cntr; + u8 reg; + + sdhci_set_power(host, mode, vdd); + + if (mode == MMC_POWER_OFF) + return; + + /* + * Bus power might not enable after D3 -> D0 transition due to the + * present state not yet having propagated. Retry for up to 2ms. + */ + for (cntr = 0; cntr < SDHCI_INTEL_PWR_TIMEOUT_CNT; cntr++) { + reg = sdhci_readb(host, SDHCI_POWER_CONTROL); + if (reg & SDHCI_POWER_ON) + break; + udelay(SDHCI_INTEL_PWR_TIMEOUT_UDELAY); + reg |= SDHCI_POWER_ON; + sdhci_writeb(host, reg, SDHCI_POWER_CONTROL); + } +} + +static const struct sdhci_ops sdhci_intel_byt_ops = { + .set_clock = sdhci_set_clock, + .set_power = sdhci_intel_set_power, + .enable_dma = sdhci_pci_enable_dma, + .set_bus_width = sdhci_pci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, + .hw_reset = sdhci_pci_hw_reset, +}; + static void byt_read_dsm(struct sdhci_pci_slot *slot) { struct intel_host *intel_host = sdhci_pci_priv(slot); @@ -606,44 +644,6 @@ static int byt_sd_probe_slot(struct sdhci_pci_slot *slot) return 0; } -#define SDHCI_INTEL_PWR_TIMEOUT_CNT 20 -#define SDHCI_INTEL_PWR_TIMEOUT_UDELAY 100 - -static void sdhci_intel_set_power(struct sdhci_host *host, unsigned char mode, - unsigned short vdd) -{ - int cntr; - u8 reg; - - sdhci_set_power(host, mode, vdd); - - if (mode == MMC_POWER_OFF) - return; - - /* - * Bus power might not enable after D3 -> D0 transition due to the - * present state not yet having propagated. Retry for up to 2ms. - */ - for (cntr = 0; cntr < SDHCI_INTEL_PWR_TIMEOUT_CNT; cntr++) { - reg = sdhci_readb(host, SDHCI_POWER_CONTROL); - if (reg & SDHCI_POWER_ON) - break; - udelay(SDHCI_INTEL_PWR_TIMEOUT_UDELAY); - reg |= SDHCI_POWER_ON; - sdhci_writeb(host, reg, SDHCI_POWER_CONTROL); - } -} - -static const struct sdhci_ops sdhci_intel_byt_ops = { - .set_clock = sdhci_set_clock, - .set_power = sdhci_intel_set_power, - .enable_dma = sdhci_pci_enable_dma, - .set_bus_width = sdhci_pci_set_bus_width, - .reset = sdhci_reset, - .set_uhs_signaling = sdhci_set_uhs_signaling, - .hw_reset = sdhci_pci_hw_reset, -}; - static const struct sdhci_pci_fixes sdhci_intel_byt_emmc = { .allow_runtime_pm = true, .probe_slot = byt_emmc_probe_slot, From 5f07ef8f603ace496ca8c20eef446c5ae7a10474 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 16 Mar 2017 11:56:02 +0100 Subject: [PATCH 061/148] mmc: tmio: always unmap DMA before waiting for interrupt In the (maybe academical) case, we don't get a DATAEND interrupt after DMA completed, we will wait endlessly for the completion to complete. This is not bad per se, since we have a more generic completion tracking a timeout. In that rare case, however, the DMA buffer will not get unmapped and we have a leak. Reorder the code, so unmapping will always take place. Signed-off-by: Wolfram Sang Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc_dma.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc_dma.c b/drivers/mmc/host/tmio_mmc_dma.c index c7684fa91f1f..e2093db2b7ff 100644 --- a/drivers/mmc/host/tmio_mmc_dma.c +++ b/drivers/mmc/host/tmio_mmc_dma.c @@ -47,8 +47,6 @@ static void tmio_mmc_dma_callback(void *arg) { struct tmio_mmc_host *host = arg; - wait_for_completion(&host->dma_dataend); - spin_lock_irq(&host->lock); if (!host->data) @@ -63,6 +61,11 @@ static void tmio_mmc_dma_callback(void *arg) host->sg_ptr, host->sg_len, DMA_TO_DEVICE); + spin_unlock_irq(&host->lock); + + wait_for_completion(&host->dma_dataend); + + spin_lock_irq(&host->lock); tmio_mmc_do_data_irq(host); out: spin_unlock_irq(&host->lock); From 43b0b361b0170030603cf76f70b099f3323edcf3 Mon Sep 17 00:00:00 2001 From: Masaharu Hayakawa Date: Fri, 17 Mar 2017 10:04:50 +0100 Subject: [PATCH 062/148] mmc: tmio: always get number of taps Current code gets number of taps only once and keeps the value. This is not correct, we need to obtain it every time before executing tuning, so remove the outer if-block. Signed-off-by: Masaharu Hayakawa [wsa: extracted from a larger patch and reworded commit message] Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc_pio.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index 5b01d22932cd..a2d92f10501b 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -815,16 +815,14 @@ static int tmio_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode) struct tmio_mmc_host *host = mmc_priv(mmc); int i, ret = 0; - if (!host->tap_num) { - if (!host->init_tuning || !host->select_tuning) - /* Tuning is not supported */ - goto out; + if (!host->init_tuning || !host->select_tuning) + /* Tuning is not supported */ + goto out; - host->tap_num = host->init_tuning(host); - if (!host->tap_num) - /* Tuning is not supported */ - goto out; - } + host->tap_num = host->init_tuning(host); + if (!host->tap_num) + /* Tuning is not supported */ + goto out; if (host->tap_num * 2 >= sizeof(host->taps) * BITS_PER_BYTE) { dev_warn_once(&host->pdev->dev, From 474e25e15f508889d4f8c672ab54323743ee595b Mon Sep 17 00:00:00 2001 From: Chris Brandt Date: Wed, 22 Mar 2017 10:42:09 -0400 Subject: [PATCH 063/148] mmc: sh_mmcif: Document r7s72100 DT bindings Signed-off-by: Chris Brandt Reviewed-by: Geert Uytterhoeven Reviewed-by: Simon Horman Acked-by: Rob Herring Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/renesas,mmcif.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/renesas,mmcif.txt b/Documentation/devicetree/bindings/mmc/renesas,mmcif.txt index e4ba92aa035e..c32dc5a9dbe6 100644 --- a/Documentation/devicetree/bindings/mmc/renesas,mmcif.txt +++ b/Documentation/devicetree/bindings/mmc/renesas,mmcif.txt @@ -8,6 +8,7 @@ Required properties: - compatible: should be "renesas,mmcif-", "renesas,sh-mmcif" as a fallback. Examples with are: + - "renesas,mmcif-r7s72100" for the MMCIF found in r7s72100 SoCs - "renesas,mmcif-r8a73a4" for the MMCIF found in r8a73a4 SoCs - "renesas,mmcif-r8a7740" for the MMCIF found in r8a7740 SoCs - "renesas,mmcif-r8a7778" for the MMCIF found in r8a7778 SoCs @@ -17,6 +18,13 @@ Required properties: - "renesas,mmcif-r8a7794" for the MMCIF found in r8a7794 SoCs - "renesas,mmcif-sh73a0" for the MMCIF found in sh73a0 SoCs +- interrupts: Some SoCs have only 1 shared interrupt, while others have either + 2 or 3 individual interrupts (error, int, card detect). Below is the number + of interrupts for each SoC: + 1: r8a73a4, r8a7778, r8a7790, r8a7791, r8a7793, r8a7794 + 2: r8a7740, sh73a0 + 3: r7s72100 + - clocks: reference to the functional clock - dmas: reference to the DMA channels, one per channel name listed in the From 7aceaf566e1d6f7bf29516d680ee883697909cc7 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Mar 2017 21:59:29 +0100 Subject: [PATCH 064/148] mmc: sdio: improve mmc_io_rw_extended Improve mmc_io_rw_extended a little: - using DIV_ROUND_UP achieves the same but is better readable - simplify code by using sg_set_buf - simplify one statement by using -= operator Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson --- drivers/mmc/core/sdio_ops.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index 3c0d3ab4324c..abaaba38514f 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -152,7 +152,7 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; left_size = data.blksz * data.blocks; - nents = (left_size - 1) / seg_size + 1; + nents = DIV_ROUND_UP(left_size, seg_size); if (nents > 1) { if (sg_alloc_table(&sgtable, nents, GFP_KERNEL)) return -ENOMEM; @@ -161,10 +161,9 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, data.sg_len = nents; for_each_sg(data.sg, sg_ptr, data.sg_len, i) { - sg_set_page(sg_ptr, virt_to_page(buf + (i * seg_size)), - min(seg_size, left_size), - offset_in_page(buf + (i * seg_size))); - left_size = left_size - seg_size; + sg_set_buf(sg_ptr, buf + i * seg_size, + min(seg_size, left_size)); + left_size -= seg_size; } } else { data.sg = &sg; From 50fde5637b6b66555036bd815c01e0aa38d38859 Mon Sep 17 00:00:00 2001 From: yong mao Date: Wed, 15 Mar 2017 15:26:38 +0800 Subject: [PATCH 065/148] mmc: dt-bindings: update Mediatek MMC bindings Add description for mediatek,hs200-cmd-int-delay Add description for mediatek,hs400-cmd-int-delay Add description for mediatek,hs400-cmd-resp-sel-rising Signed-off-by: Yong Mao Acked-by: Rob Herring Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/mtk-sd.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/mtk-sd.txt b/Documentation/devicetree/bindings/mmc/mtk-sd.txt index 0120c7f1109c..4182ea36ca5b 100644 --- a/Documentation/devicetree/bindings/mmc/mtk-sd.txt +++ b/Documentation/devicetree/bindings/mmc/mtk-sd.txt @@ -21,6 +21,15 @@ Optional properties: - assigned-clocks: PLL of the source clock - assigned-clock-parents: parent of source clock, used for HS400 mode to get 400Mhz source clock - hs400-ds-delay: HS400 DS delay setting +- mediatek,hs200-cmd-int-delay: HS200 command internal delay setting. + This field has total 32 stages. + The value is an integer from 0 to 31. +- mediatek,hs400-cmd-int-delay: HS400 command internal delay setting + This field has total 32 stages. + The value is an integer from 0 to 31. +- mediatek,hs400-cmd-resp-sel-rising: HS400 command response sample selection + If present,HS400 command responses are sampled on rising edges. + If not present,HS400 command responses are sampled on falling edges. Examples: mmc0: mmc@11230000 { @@ -38,4 +47,7 @@ mmc0: mmc@11230000 { assigned-clocks = <&topckgen CLK_TOP_MSDC50_0_SEL>; assigned-clock-parents = <&topckgen CLK_TOP_MSDCPLL_D2>; hs400-ds-delay = <0x14015>; + mediatek,hs200-cmd-int-delay = <26>; + mediatek,hs400-cmd-int-delay = <14>; + mediatek,hs400-cmd-resp-sel-rising; }; From 1ede5cb88a29bba1aad1b68965c5fc8e00b20ed9 Mon Sep 17 00:00:00 2001 From: yong mao Date: Wed, 15 Mar 2017 15:26:40 +0800 Subject: [PATCH 066/148] mmc: mediatek: Use data tune for CMD line tune If we don't select a set of better parameters for our emmc host, It may easily occur CMD response CRC error. And also it may cause cannot boot up issue. Fot getting a set of better parameters, our emmc host supports data tune mechanism.Therefore, our emmc driver also should change to use data tune for CMD line. Because our emmc host use the different clock source to sample the CMD signal between HS200 and HS400 mode, the parameters are also different between these two modes. Separate cmd internal delay for HS200/HS400 mode. This change can fix "System can not boot up" issue. Signed-off-by: Yong Mao Signed-off-by: Chaotian Jing Signed-off-by: Ulf Hansson --- drivers/mmc/host/mtk-sd.c | 168 ++++++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 16 deletions(-) diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index b235d8da0602..0789d58e803c 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -76,6 +76,7 @@ #define MSDC_PATCH_BIT1 0xb4 #define MSDC_PAD_TUNE 0xec #define PAD_DS_TUNE 0x188 +#define PAD_CMD_TUNE 0x18c #define EMMC50_CFG0 0x208 /*--------------------------------------------------------------------------*/ @@ -211,13 +212,18 @@ #define MSDC_PATCH_BIT_SPCPUSH (0x1 << 29) /* RW */ #define MSDC_PATCH_BIT_DECRCTMO (0x1 << 30) /* RW */ +#define MSDC_PAD_TUNE_DATWRDLY (0x1f << 0) /* RW */ #define MSDC_PAD_TUNE_DATRRDLY (0x1f << 8) /* RW */ #define MSDC_PAD_TUNE_CMDRDLY (0x1f << 16) /* RW */ +#define MSDC_PAD_TUNE_CMDRRDLY (0x1f << 22) /* RW */ +#define MSDC_PAD_TUNE_CLKTDLY (0x1f << 27) /* RW */ #define PAD_DS_TUNE_DLY1 (0x1f << 2) /* RW */ #define PAD_DS_TUNE_DLY2 (0x1f << 7) /* RW */ #define PAD_DS_TUNE_DLY3 (0x1f << 12) /* RW */ +#define PAD_CMD_TUNE_RX_DLY3 (0x1f << 1) /* RW */ + #define EMMC50_CFG_PADCMD_LATCHCK (0x1 << 0) /* RW */ #define EMMC50_CFG_CRCSTS_EDGE (0x1 << 3) /* RW */ #define EMMC50_CFG_CFCSTS_SEL (0x1 << 4) /* RW */ @@ -285,12 +291,14 @@ struct msdc_save_para { u32 patch_bit0; u32 patch_bit1; u32 pad_ds_tune; + u32 pad_cmd_tune; u32 emmc50_cfg0; }; struct msdc_tune_para { u32 iocon; u32 pad_tune; + u32 pad_cmd_tune; }; struct msdc_delay_phase { @@ -332,6 +340,10 @@ struct msdc_host { unsigned char timing; bool vqmmc_enabled; u32 hs400_ds_delay; + u32 hs200_cmd_int_delay; /* cmd internal delay for HS200/SDR104 */ + u32 hs400_cmd_int_delay; /* cmd internal delay for HS400 */ + bool hs400_cmd_resp_sel_rising; + /* cmd response sample selection for HS400 */ bool hs400_mode; /* current eMMC will run at hs400 mode */ struct msdc_save_para save_para; /* used when gate HCLK */ struct msdc_tune_para def_tune_para; /* default tune setting */ @@ -601,8 +613,14 @@ static void msdc_set_mclk(struct msdc_host *host, unsigned char timing, u32 hz) } else { writel(host->saved_tune_para.iocon, host->base + MSDC_IOCON); writel(host->saved_tune_para.pad_tune, host->base + MSDC_PAD_TUNE); + writel(host->saved_tune_para.pad_cmd_tune, + host->base + PAD_CMD_TUNE); } + if (timing == MMC_TIMING_MMC_HS400) + sdr_set_field(host->base + PAD_CMD_TUNE, + MSDC_PAD_TUNE_CMDRRDLY, + host->hs400_cmd_int_delay); dev_dbg(host->dev, "sclk: %d, timing: %d\n", host->sclk, timing); } @@ -1303,7 +1321,7 @@ static struct msdc_delay_phase get_best_delay(struct msdc_host *host, u32 delay) len_final = len; } start += len ? len : 1; - if (len >= 8 && start_final < 4) + if (len >= 12 && start_final < 4) break; } @@ -1326,36 +1344,67 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode) struct msdc_host *host = mmc_priv(mmc); u32 rise_delay = 0, fall_delay = 0; struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0,}; + struct msdc_delay_phase internal_delay_phase; u8 final_delay, final_maxlen; + u32 internal_delay = 0; int cmd_err; - int i; + int i, j; + + if (mmc->ios.timing == MMC_TIMING_MMC_HS200 || + mmc->ios.timing == MMC_TIMING_UHS_SDR104) + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_CMDRRDLY, + host->hs200_cmd_int_delay); sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); for (i = 0 ; i < PAD_DELAY_MAX; i++) { sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRDLY, i); - mmc_send_tuning(mmc, opcode, &cmd_err); - if (!cmd_err) - rise_delay |= (1 << i); + /* + * Using the same parameters, it may sometimes pass the test, + * but sometimes it may fail. To make sure the parameters are + * more stable, we test each set of parameters 3 times. + */ + for (j = 0; j < 3; j++) { + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) { + rise_delay |= (1 << i); + } else { + rise_delay &= ~(1 << i); + break; + } + } } final_rise_delay = get_best_delay(host, rise_delay); /* if rising edge has enough margin, then do not scan falling edge */ - if (final_rise_delay.maxlen >= 10 || - (final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4)) + if (final_rise_delay.maxlen >= 12 && final_rise_delay.start < 4) goto skip_fall; sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); for (i = 0; i < PAD_DELAY_MAX; i++) { sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRDLY, i); - mmc_send_tuning(mmc, opcode, &cmd_err); - if (!cmd_err) - fall_delay |= (1 << i); + /* + * Using the same parameters, it may sometimes pass the test, + * but sometimes it may fail. To make sure the parameters are + * more stable, we test each set of parameters 3 times. + */ + for (j = 0; j < 3; j++) { + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) { + fall_delay |= (1 << i); + } else { + fall_delay &= ~(1 << i); + break; + } + } } final_fall_delay = get_best_delay(host, fall_delay); skip_fall: final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen); + if (final_fall_delay.maxlen >= 12 && final_fall_delay.start < 4) + final_maxlen = final_fall_delay.maxlen; if (final_maxlen == final_rise_delay.maxlen) { sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRDLY, @@ -1367,7 +1416,71 @@ skip_fall: final_fall_delay.final_phase); final_delay = final_fall_delay.final_phase; } + if (host->hs200_cmd_int_delay) + goto skip_internal; + for (i = 0; i < PAD_DELAY_MAX; i++) { + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_CMDRRDLY, i); + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) + internal_delay |= (1 << i); + } + dev_dbg(host->dev, "Final internal delay: 0x%x\n", internal_delay); + internal_delay_phase = get_best_delay(host, internal_delay); + sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRRDLY, + internal_delay_phase.final_phase); +skip_internal: + dev_dbg(host->dev, "Final cmd pad delay: %x\n", final_delay); + return final_delay == 0xff ? -EIO : 0; +} + +static int hs400_tune_response(struct mmc_host *mmc, u32 opcode) +{ + struct msdc_host *host = mmc_priv(mmc); + u32 cmd_delay = 0; + struct msdc_delay_phase final_cmd_delay = { 0,}; + u8 final_delay; + int cmd_err; + int i, j; + + /* select EMMC50 PAD CMD tune */ + sdr_set_bits(host->base + PAD_CMD_TUNE, BIT(0)); + + if (mmc->ios.timing == MMC_TIMING_MMC_HS200 || + mmc->ios.timing == MMC_TIMING_UHS_SDR104) + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_CMDRRDLY, + host->hs200_cmd_int_delay); + + if (host->hs400_cmd_resp_sel_rising) + sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); + else + sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); + for (i = 0 ; i < PAD_DELAY_MAX; i++) { + sdr_set_field(host->base + PAD_CMD_TUNE, + PAD_CMD_TUNE_RX_DLY3, i); + /* + * Using the same parameters, it may sometimes pass the test, + * but sometimes it may fail. To make sure the parameters are + * more stable, we test each set of parameters 3 times. + */ + for (j = 0; j < 3; j++) { + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) { + cmd_delay |= (1 << i); + } else { + cmd_delay &= ~(1 << i); + break; + } + } + } + final_cmd_delay = get_best_delay(host, cmd_delay); + sdr_set_field(host->base + PAD_CMD_TUNE, PAD_CMD_TUNE_RX_DLY3, + final_cmd_delay.final_phase); + final_delay = final_cmd_delay.final_phase; + + dev_dbg(host->dev, "Final cmd pad delay: %x\n", final_delay); return final_delay == 0xff ? -EIO : 0; } @@ -1390,7 +1503,7 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode) } final_rise_delay = get_best_delay(host, rise_delay); /* if rising edge has enough margin, then do not scan falling edge */ - if (final_rise_delay.maxlen >= 10 || + if (final_rise_delay.maxlen >= 12 || (final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4)) goto skip_fall; @@ -1423,6 +1536,7 @@ skip_fall: final_delay = final_fall_delay.final_phase; } + dev_dbg(host->dev, "Final data pad delay: %x\n", final_delay); return final_delay == 0xff ? -EIO : 0; } @@ -1431,7 +1545,10 @@ static int msdc_execute_tuning(struct mmc_host *mmc, u32 opcode) struct msdc_host *host = mmc_priv(mmc); int ret; - ret = msdc_tune_response(mmc, opcode); + if (host->hs400_mode) + ret = hs400_tune_response(mmc, opcode); + else + ret = msdc_tune_response(mmc, opcode); if (ret == -EIO) { dev_err(host->dev, "Tune response fail!\n"); return ret; @@ -1444,6 +1561,7 @@ static int msdc_execute_tuning(struct mmc_host *mmc, u32 opcode) host->saved_tune_para.iocon = readl(host->base + MSDC_IOCON); host->saved_tune_para.pad_tune = readl(host->base + MSDC_PAD_TUNE); + host->saved_tune_para.pad_cmd_tune = readl(host->base + PAD_CMD_TUNE); return ret; } @@ -1478,6 +1596,25 @@ static struct mmc_host_ops mt_msdc_ops = { .hw_reset = msdc_hw_reset, }; +static void msdc_of_property_parse(struct platform_device *pdev, + struct msdc_host *host) +{ + of_property_read_u32(pdev->dev.of_node, "hs400-ds-delay", + &host->hs400_ds_delay); + + of_property_read_u32(pdev->dev.of_node, "mediatek,hs200-cmd-int-delay", + &host->hs200_cmd_int_delay); + + of_property_read_u32(pdev->dev.of_node, "mediatek,hs400-cmd-int-delay", + &host->hs400_cmd_int_delay); + + if (of_property_read_bool(pdev->dev.of_node, + "mediatek,hs400-cmd-resp-sel-rising")) + host->hs400_cmd_resp_sel_rising = true; + else + host->hs400_cmd_resp_sel_rising = false; +} + static int msdc_drv_probe(struct platform_device *pdev) { struct mmc_host *mmc; @@ -1549,10 +1686,7 @@ static int msdc_drv_probe(struct platform_device *pdev) goto host_free; } - if (!of_property_read_u32(pdev->dev.of_node, "hs400-ds-delay", - &host->hs400_ds_delay)) - dev_dbg(&pdev->dev, "hs400-ds-delay: %x\n", - host->hs400_ds_delay); + msdc_of_property_parse(pdev, host); host->dev = &pdev->dev; host->mmc = mmc; @@ -1664,6 +1798,7 @@ static void msdc_save_reg(struct msdc_host *host) host->save_para.patch_bit0 = readl(host->base + MSDC_PATCH_BIT); host->save_para.patch_bit1 = readl(host->base + MSDC_PATCH_BIT1); host->save_para.pad_ds_tune = readl(host->base + PAD_DS_TUNE); + host->save_para.pad_cmd_tune = readl(host->base + PAD_CMD_TUNE); host->save_para.emmc50_cfg0 = readl(host->base + EMMC50_CFG0); } @@ -1676,6 +1811,7 @@ static void msdc_restore_reg(struct msdc_host *host) writel(host->save_para.patch_bit0, host->base + MSDC_PATCH_BIT); writel(host->save_para.patch_bit1, host->base + MSDC_PATCH_BIT1); writel(host->save_para.pad_ds_tune, host->base + PAD_DS_TUNE); + writel(host->save_para.pad_cmd_tune, host->base + PAD_CMD_TUNE); writel(host->save_para.emmc50_cfg0, host->base + EMMC50_CFG0); } From ec31e53c2aab68b93381b194f70626f082bbc9d0 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Mar 2017 22:18:37 +0100 Subject: [PATCH 067/148] mmc: meson-gx: remove unneeded configuration bit resets The variables holding the configuration bits are zero-initialized. So we can remove all bit resets. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson Reviewed-by: Kevin Hilman --- drivers/mmc/host/meson-gx-mmc.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index bd075fef08d4..04a55577f1ab 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -441,7 +441,6 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) /* Response */ if (cmd->flags & MMC_RSP_PRESENT) { - desc->cmd_cfg &= ~CMD_CFG_NO_RESP; if (cmd->flags & MMC_RSP_136) desc->cmd_cfg |= CMD_CFG_RESP_128; desc->cmd_cfg |= CMD_CFG_RESP_NUM; @@ -479,7 +478,6 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) writel(cfg, host->regs + SD_EMMC_CFG); } } else { - desc->cmd_cfg &= ~CMD_CFG_BLOCK_MODE; desc->cmd_cfg |= (cmd->data->blksz & CMD_CFG_LENGTH_MASK) << CMD_CFG_LENGTH_SHIFT; @@ -494,15 +492,12 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) host->bounce_buf, xfer_bytes); cmd->data->bytes_xfered = xfer_bytes; dma_wmb(); - } else { - desc->cmd_cfg &= ~CMD_CFG_DATA_WR; } desc->cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT_DATA); } else { - desc->cmd_cfg &= ~CMD_CFG_DATA_IO; cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT); } desc->cmd_cfg |= (cmd_cfg_timeout & CMD_CFG_TIMEOUT_MASK) << From 00412ddc4ae929419a64bc9766205aac0467e664 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Mar 2017 22:33:44 +0100 Subject: [PATCH 068/148] mmc: meson-gx: replace cmd->data in meson_mmc_start_cmd Replace cmd->data with a local variable to simplify code a little. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson Reviewed-by: Kevin Hilman --- drivers/mmc/host/meson-gx-mmc.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 04a55577f1ab..1b20ec1a1a90 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -424,6 +424,7 @@ static void meson_mmc_request_done(struct mmc_host *mmc, static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) { struct meson_host *host = mmc_priv(mmc); + struct mmc_data *data = cmd->data; struct sd_emmc_desc *desc, desc_tmp; u32 cfg; u8 blk_len, cmd_cfg_timeout; @@ -456,41 +457,41 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) } /* data? */ - if (cmd->data) { + if (data) { desc->cmd_cfg |= CMD_CFG_DATA_IO; - if (cmd->data->blocks > 1) { + if (data->blocks > 1) { desc->cmd_cfg |= CMD_CFG_BLOCK_MODE; desc->cmd_cfg |= - (cmd->data->blocks & CMD_CFG_LENGTH_MASK) << + (data->blocks & CMD_CFG_LENGTH_MASK) << CMD_CFG_LENGTH_SHIFT; /* check if block-size matches, if not update */ cfg = readl(host->regs + SD_EMMC_CFG); blk_len = cfg & (CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT); blk_len >>= CFG_BLK_LEN_SHIFT; - if (blk_len != ilog2(cmd->data->blksz)) { + if (blk_len != ilog2(data->blksz)) { dev_dbg(host->dev, "%s: update blk_len %d -> %d\n", __func__, blk_len, - ilog2(cmd->data->blksz)); - blk_len = ilog2(cmd->data->blksz); + ilog2(data->blksz)); + blk_len = ilog2(data->blksz); cfg &= ~(CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT); cfg |= blk_len << CFG_BLK_LEN_SHIFT; writel(cfg, host->regs + SD_EMMC_CFG); } } else { desc->cmd_cfg |= - (cmd->data->blksz & CMD_CFG_LENGTH_MASK) << + (data->blksz & CMD_CFG_LENGTH_MASK) << CMD_CFG_LENGTH_SHIFT; } - cmd->data->bytes_xfered = 0; - xfer_bytes = cmd->data->blksz * cmd->data->blocks; - if (cmd->data->flags & MMC_DATA_WRITE) { + data->bytes_xfered = 0; + xfer_bytes = data->blksz * data->blocks; + if (data->flags & MMC_DATA_WRITE) { desc->cmd_cfg |= CMD_CFG_DATA_WR; WARN_ON(xfer_bytes > host->bounce_buf_size); - sg_copy_to_buffer(cmd->data->sg, cmd->data->sg_len, + sg_copy_to_buffer(data->sg, data->sg_len, host->bounce_buf, xfer_bytes); - cmd->data->bytes_xfered = xfer_bytes; + data->bytes_xfered = xfer_bytes; dma_wmb(); } From a322febe37432443b8d0adb0b630a9f91961152c Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Mar 2017 22:33:47 +0100 Subject: [PATCH 069/148] mmc: meson-gx: improve variable usage in meson_mmc_start_cmd Simplify the code by using two variables instead of a struct sd_emmc_desc. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson Reviewed-by: Kevin Hilman --- drivers/mmc/host/meson-gx-mmc.c | 55 ++++++++++++++------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 1b20ec1a1a90..8bf3eb540eb7 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -425,45 +425,39 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) { struct meson_host *host = mmc_priv(mmc); struct mmc_data *data = cmd->data; - struct sd_emmc_desc *desc, desc_tmp; - u32 cfg; + u32 cfg, cmd_cfg = 0, cmd_data = 0; u8 blk_len, cmd_cfg_timeout; unsigned int xfer_bytes = 0; /* Setup descriptors */ dma_rmb(); - desc = &desc_tmp; - memset(desc, 0, sizeof(struct sd_emmc_desc)); - desc->cmd_cfg |= (cmd->opcode & CMD_CFG_CMD_INDEX_MASK) << - CMD_CFG_CMD_INDEX_SHIFT; - desc->cmd_cfg |= CMD_CFG_OWNER; /* owned by CPU */ - desc->cmd_arg = cmd->arg; + cmd_cfg |= (cmd->opcode & CMD_CFG_CMD_INDEX_MASK) << + CMD_CFG_CMD_INDEX_SHIFT; + cmd_cfg |= CMD_CFG_OWNER; /* owned by CPU */ /* Response */ if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) - desc->cmd_cfg |= CMD_CFG_RESP_128; - desc->cmd_cfg |= CMD_CFG_RESP_NUM; - desc->cmd_resp = 0; + cmd_cfg |= CMD_CFG_RESP_128; + cmd_cfg |= CMD_CFG_RESP_NUM; if (!(cmd->flags & MMC_RSP_CRC)) - desc->cmd_cfg |= CMD_CFG_RESP_NOCRC; + cmd_cfg |= CMD_CFG_RESP_NOCRC; if (cmd->flags & MMC_RSP_BUSY) - desc->cmd_cfg |= CMD_CFG_R1B; + cmd_cfg |= CMD_CFG_R1B; } else { - desc->cmd_cfg |= CMD_CFG_NO_RESP; + cmd_cfg |= CMD_CFG_NO_RESP; } /* data? */ if (data) { - desc->cmd_cfg |= CMD_CFG_DATA_IO; + cmd_cfg |= CMD_CFG_DATA_IO; if (data->blocks > 1) { - desc->cmd_cfg |= CMD_CFG_BLOCK_MODE; - desc->cmd_cfg |= - (data->blocks & CMD_CFG_LENGTH_MASK) << - CMD_CFG_LENGTH_SHIFT; + cmd_cfg |= CMD_CFG_BLOCK_MODE; + cmd_cfg |= (data->blocks & CMD_CFG_LENGTH_MASK) << + CMD_CFG_LENGTH_SHIFT; /* check if block-size matches, if not update */ cfg = readl(host->regs + SD_EMMC_CFG); @@ -479,15 +473,14 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) writel(cfg, host->regs + SD_EMMC_CFG); } } else { - desc->cmd_cfg |= - (data->blksz & CMD_CFG_LENGTH_MASK) << - CMD_CFG_LENGTH_SHIFT; + cmd_cfg |= (data->blksz & CMD_CFG_LENGTH_MASK) << + CMD_CFG_LENGTH_SHIFT; } data->bytes_xfered = 0; xfer_bytes = data->blksz * data->blocks; if (data->flags & MMC_DATA_WRITE) { - desc->cmd_cfg |= CMD_CFG_DATA_WR; + cmd_cfg |= CMD_CFG_DATA_WR; WARN_ON(xfer_bytes > host->bounce_buf_size); sg_copy_to_buffer(data->sg, data->sg_len, host->bounce_buf, xfer_bytes); @@ -495,24 +488,24 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) dma_wmb(); } - desc->cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; + cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT_DATA); } else { cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT); } - desc->cmd_cfg |= (cmd_cfg_timeout & CMD_CFG_TIMEOUT_MASK) << - CMD_CFG_TIMEOUT_SHIFT; + cmd_cfg |= (cmd_cfg_timeout & CMD_CFG_TIMEOUT_MASK) << + CMD_CFG_TIMEOUT_SHIFT; host->cmd = cmd; /* Last descriptor */ - desc->cmd_cfg |= CMD_CFG_END_OF_CHAIN; - writel(desc->cmd_cfg, host->regs + SD_EMMC_CMD_CFG); - writel(desc->cmd_data, host->regs + SD_EMMC_CMD_DAT); - writel(desc->cmd_resp, host->regs + SD_EMMC_CMD_RSP); + cmd_cfg |= CMD_CFG_END_OF_CHAIN; + writel(cmd_cfg, host->regs + SD_EMMC_CMD_CFG); + writel(cmd_data, host->regs + SD_EMMC_CMD_DAT); + writel(0, host->regs + SD_EMMC_CMD_RSP); wmb(); /* ensure descriptor is written before kicked */ - writel(desc->cmd_arg, host->regs + SD_EMMC_CMD_ARG); + writel(cmd->arg, host->regs + SD_EMMC_CMD_ARG); } static void meson_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) From 1f8066d90739ebfdf70a5debbc5fed22fdd17465 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Mar 2017 22:33:50 +0100 Subject: [PATCH 070/148] mmc: meson-gx: improve response reading Response reading can be slightly improved by doing it in just one place. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson Reviewed-by: Kevin Hilman --- drivers/mmc/host/meson-gx-mmc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 8bf3eb540eb7..c4ddd8201825 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -562,6 +562,8 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id) goto out; } + meson_mmc_read_resp(host->mmc, cmd); + cmd->error = 0; if (status & IRQ_RXD_ERR_MASK) { dev_dbg(host->dev, "Unhandled IRQ: RXD error\n"); @@ -608,10 +610,8 @@ out: /* ack all (enabled) interrupts */ writel(status, host->regs + SD_EMMC_STATUS); - if (ret == IRQ_HANDLED) { - meson_mmc_read_resp(host->mmc, cmd); + if (ret == IRQ_HANDLED) meson_mmc_request_done(host->mmc, cmd->mrq); - } spin_unlock(&host->lock); return ret; @@ -636,7 +636,6 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) data->bytes_xfered = xfer_bytes; } - meson_mmc_read_resp(host->mmc, cmd); if (!data || !data->stop || cmd->mrq->sbc) meson_mmc_request_done(host->mmc, cmd->mrq); else From 2c8d96a46dfd45b724465e53a5a9542bcb78cfce Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Mar 2017 22:33:53 +0100 Subject: [PATCH 071/148] mmc: meson-gx: improve setting data->bytes_xfered Centralize setting data->bytes_xfered in one place and make sure that the number of transfererd bytes is set only if we receive the end-of-chain interrupt and there was no error. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson Reviewed-by: Kevin Hilman --- drivers/mmc/host/meson-gx-mmc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index c4ddd8201825..87303404a7df 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -484,7 +484,6 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) WARN_ON(xfer_bytes > host->bounce_buf_size); sg_copy_to_buffer(data->sg, data->sg_len, host->bounce_buf, xfer_bytes); - data->bytes_xfered = xfer_bytes; dma_wmb(); } @@ -539,6 +538,7 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id) { struct meson_host *host = dev_id; struct mmc_command *cmd; + struct mmc_data *data; u32 irq_en, status, raw_status; irqreturn_t ret = IRQ_HANDLED; @@ -550,6 +550,8 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id) if (WARN_ON(!cmd)) return IRQ_NONE; + data = cmd->data; + spin_lock(&host->lock); irq_en = readl(host->regs + SD_EMMC_IRQ_EN); raw_status = readl(host->regs + SD_EMMC_STATUS); @@ -590,9 +592,11 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id) if (status & IRQ_SDIO) dev_dbg(host->dev, "Unhandled IRQ: SDIO.\n"); - if (status & (IRQ_END_OF_CHAIN | IRQ_RESP_STATUS)) + if (status & (IRQ_END_OF_CHAIN | IRQ_RESP_STATUS)) { + if (data && !cmd->error) + data->bytes_xfered = data->blksz * data->blocks; ret = IRQ_WAKE_THREAD; - else { + } else { dev_warn(host->dev, "Unknown IRQ! status=0x%04x: MMC CMD%u arg=0x%08x flags=0x%08x stop=%d\n", status, cmd->opcode, cmd->arg, cmd->flags, cmd->mrq->stop ? 1 : 0); @@ -633,7 +637,6 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) WARN_ON(xfer_bytes > host->bounce_buf_size); sg_copy_from_buffer(data->sg, data->sg_len, host->bounce_buf, xfer_bytes); - data->bytes_xfered = xfer_bytes; } if (!data || !data->stop || cmd->mrq->sbc) From a744c6fe13beef87cd541f1dcaffe97f2af0c908 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Mar 2017 22:34:01 +0100 Subject: [PATCH 072/148] mmc: meson-gx: simplify setting timeout configuration parameters We don't need variable cmd_cfg_timeout, so remove it and simplify the code a little. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson Reviewed-by: Kevin Hilman --- drivers/mmc/host/meson-gx-mmc.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 87303404a7df..b917765c3815 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -426,7 +426,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) struct meson_host *host = mmc_priv(mmc); struct mmc_data *data = cmd->data; u32 cfg, cmd_cfg = 0, cmd_data = 0; - u8 blk_len, cmd_cfg_timeout; + u8 blk_len; unsigned int xfer_bytes = 0; /* Setup descriptors */ @@ -454,6 +454,9 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) /* data? */ if (data) { cmd_cfg |= CMD_CFG_DATA_IO; + cmd_cfg |= ilog2(SD_EMMC_CMD_TIMEOUT_DATA) << + CMD_CFG_TIMEOUT_SHIFT; + if (data->blocks > 1) { cmd_cfg |= CMD_CFG_BLOCK_MODE; cmd_cfg |= (data->blocks & CMD_CFG_LENGTH_MASK) << @@ -488,13 +491,9 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) } cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; - - cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT_DATA); } else { - cmd_cfg_timeout = ilog2(SD_EMMC_CMD_TIMEOUT); + cmd_cfg |= ilog2(SD_EMMC_CMD_TIMEOUT) << CMD_CFG_TIMEOUT_SHIFT; } - cmd_cfg |= (cmd_cfg_timeout & CMD_CFG_TIMEOUT_MASK) << - CMD_CFG_TIMEOUT_SHIFT; host->cmd = cmd; From bd675698e8eece61c02a5e436e6d5d5ac1180e46 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 16 Mar 2017 21:29:03 +0800 Subject: [PATCH 073/148] mmc: sunxi: change controller error info to debug level The controller's errors are usually normal (for example, for MMC or SDIO cards, some errors are expected to happen; and for boards without a dedicated card detect pin the error info will even flood console and hide other normal messages) and hard to understand. Change their print level to debug, thus it won't be shown to generic users. Signed-off-by: Icenowy Zheng Acked-by: Maxime Ripard Signed-off-by: Ulf Hansson --- drivers/mmc/host/sunxi-mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c index 6ffcd2838272..0391c62cc99f 100644 --- a/drivers/mmc/host/sunxi-mmc.c +++ b/drivers/mmc/host/sunxi-mmc.c @@ -489,7 +489,7 @@ static void sunxi_mmc_dump_errinfo(struct sunxi_mmc_host *host) cmd->opcode == SD_IO_RW_DIRECT)) return; - dev_err(mmc_dev(host->mmc), + dev_dbg(mmc_dev(host->mmc), "smc %d err, cmd %d,%s%s%s%s%s%s%s%s%s%s !!\n", host->mmc->index, cmd->opcode, data ? (data->flags & MMC_DATA_WRITE ? " WR" : " RD") : "", From 8cc35289227c5cbb8811048519c1703e2137d421 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 24 Mar 2017 15:50:12 +0800 Subject: [PATCH 074/148] mmc: sdhci: clarify the get_timeout_clock callback Currently the get_timeout_clock callback doesn't clearly have a statement that it needs the variant drivers to return the timeout clock rate in kHz if the SDHCI_TIMEOUT_CLK_UNIT isn't present, otherwise the variant drivers should return it in MHz. It's also very likely that further variant drivers which are going to use this callback will be confused by this situation. Given the fact that moderm sdhci variant hosts are very prone to get the timeout clock from common clock framework (actually the only three users did that), it's more natural to return the value in Hz and we make an explicit comment there. Then we put the unit conversion inside the sdhci core. Thus will improve the code and prevent further misuses. Reported-by: Anssi Hannula Signed-off-by: Shawn Lin Acked-by: Masahiro Yamada Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 4 ++-- drivers/mmc/host/sdhci-of-arasan.c | 17 +---------------- drivers/mmc/host/sdhci.c | 16 +++++++++------- drivers/mmc/host/sdhci.h | 1 + 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 48f641953844..07a27dcb42be 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -105,9 +105,9 @@ static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) { /* * Cadence's spec says the Timeout Clock Frequency is the same as the - * Base Clock Frequency. Divide it by 1000 to return a value in kHz. + * Base Clock Frequency. */ - return host->max_clk / 1000; + return host->max_clk; } static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode) diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c index 61accf0d4739..ea6b36c88ae7 100644 --- a/drivers/mmc/host/sdhci-of-arasan.c +++ b/drivers/mmc/host/sdhci-of-arasan.c @@ -157,21 +157,6 @@ static int sdhci_arasan_syscon_write(struct sdhci_host *host, return ret; } -static unsigned int sdhci_arasan_get_timeout_clock(struct sdhci_host *host) -{ - unsigned long freq; - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - - /* SDHCI timeout clock is in kHz */ - freq = DIV_ROUND_UP(clk_get_rate(pltfm_host->clk), 1000); - - /* or in MHz */ - if (host->caps & SDHCI_TIMEOUT_CLK_UNIT) - freq = DIV_ROUND_UP(freq, 1000); - - return freq; -} - static void sdhci_arasan_set_clock(struct sdhci_host *host, unsigned int clock) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -280,7 +265,7 @@ static int sdhci_arasan_voltage_switch(struct mmc_host *mmc, static struct sdhci_ops sdhci_arasan_ops = { .set_clock = sdhci_arasan_set_clock, .get_max_clock = sdhci_pltfm_clk_get_max_clock, - .get_timeout_clock = sdhci_arasan_get_timeout_clock, + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, .set_bus_width = sdhci_set_bus_width, .reset = sdhci_arasan_reset, .set_uhs_signaling = sdhci_set_uhs_signaling, diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a971fcfa6de7..147df8bcc231 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3396,20 +3396,22 @@ int sdhci_setup_host(struct sdhci_host *host) if (!(host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)) { host->timeout_clk = (host->caps & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT; + + if (host->caps & SDHCI_TIMEOUT_CLK_UNIT) + host->timeout_clk *= 1000; + if (host->timeout_clk == 0) { - if (host->ops->get_timeout_clock) { - host->timeout_clk = - host->ops->get_timeout_clock(host); - } else { + if (!host->ops->get_timeout_clock) { pr_err("%s: Hardware doesn't specify timeout clock frequency.\n", mmc_hostname(mmc)); ret = -ENODEV; goto undma; } - } - if (host->caps & SDHCI_TIMEOUT_CLK_UNIT) - host->timeout_clk *= 1000; + host->timeout_clk = + DIV_ROUND_UP(host->ops->get_timeout_clock(host), + 1000); + } if (override_timeout_clk) host->timeout_clk = override_timeout_clk; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 35b41da0a636..fdb5d7e1911f 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -561,6 +561,7 @@ struct sdhci_ops { int (*enable_dma)(struct sdhci_host *host); unsigned int (*get_max_clock)(struct sdhci_host *host); unsigned int (*get_min_clock)(struct sdhci_host *host); + /* get_timeout_clock should return clk rate in unit of Hz */ unsigned int (*get_timeout_clock)(struct sdhci_host *host); unsigned int (*get_max_timeout_count)(struct sdhci_host *host); void (*set_timeout)(struct sdhci_host *host, From 1231e7eb62224235bb92a2a39cb35c89afd474af Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 25 Mar 2017 11:23:24 +0100 Subject: [PATCH 075/148] mmc: meson-gx: use bitfield macros Use GENMASK consistently for all bit masks and switch to using the bitfield macros FIELD_GET and FIELD_PREP. This hides parts of the complexity of dealing with bit fields. Signed-off-by: Heiner Kallweit Reviewed-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 94 ++++++++++++++------------------- 1 file changed, 40 insertions(+), 54 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index b917765c3815..ab1f2d336ae2 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -36,23 +36,19 @@ #include #include #include +#include #define DRIVER_NAME "meson-gx-mmc" #define SD_EMMC_CLOCK 0x0 -#define CLK_DIV_SHIFT 0 -#define CLK_DIV_WIDTH 6 -#define CLK_DIV_MASK 0x3f +#define CLK_DIV_MASK GENMASK(5, 0) #define CLK_DIV_MAX 63 -#define CLK_SRC_SHIFT 6 -#define CLK_SRC_WIDTH 2 -#define CLK_SRC_MASK 0x3 +#define CLK_SRC_MASK GENMASK(7, 6) #define CLK_SRC_XTAL 0 /* external crystal */ #define CLK_SRC_XTAL_RATE 24000000 #define CLK_SRC_PLL 1 /* FCLK_DIV2 */ #define CLK_SRC_PLL_RATE 1000000000 -#define CLK_PHASE_SHIFT 8 -#define CLK_PHASE_MASK 0x3 +#define CLK_CORE_PHASE_MASK GENMASK(9, 8) #define CLK_PHASE_0 0 #define CLK_PHASE_90 1 #define CLK_PHASE_180 2 @@ -65,22 +61,17 @@ #define SD_EMMC_START 0x40 #define START_DESC_INIT BIT(0) #define START_DESC_BUSY BIT(1) -#define START_DESC_ADDR_SHIFT 2 -#define START_DESC_ADDR_MASK (~0x3) +#define START_DESC_ADDR_MASK GENMASK(31, 2) #define SD_EMMC_CFG 0x44 -#define CFG_BUS_WIDTH_SHIFT 0 -#define CFG_BUS_WIDTH_MASK 0x3 +#define CFG_BUS_WIDTH_MASK GENMASK(1, 0) #define CFG_BUS_WIDTH_1 0x0 #define CFG_BUS_WIDTH_4 0x1 #define CFG_BUS_WIDTH_8 0x2 #define CFG_DDR BIT(2) -#define CFG_BLK_LEN_SHIFT 4 -#define CFG_BLK_LEN_MASK 0xf -#define CFG_RESP_TIMEOUT_SHIFT 8 -#define CFG_RESP_TIMEOUT_MASK 0xf -#define CFG_RC_CC_SHIFT 12 -#define CFG_RC_CC_MASK 0xf +#define CFG_BLK_LEN_MASK GENMASK(7, 4) +#define CFG_RESP_TIMEOUT_MASK GENMASK(11, 8) +#define CFG_RC_CC_MASK GENMASK(15, 12) #define CFG_STOP_CLOCK BIT(22) #define CFG_CLK_ALWAYS_ON BIT(18) #define CFG_CHK_DS BIT(20) @@ -90,9 +81,8 @@ #define STATUS_BUSY BIT(31) #define SD_EMMC_IRQ_EN 0x4c -#define IRQ_EN_MASK 0x3fff -#define IRQ_RXD_ERR_SHIFT 0 -#define IRQ_RXD_ERR_MASK 0xff +#define IRQ_EN_MASK GENMASK(13, 0) +#define IRQ_RXD_ERR_MASK GENMASK(7, 0) #define IRQ_TXD_ERR BIT(8) #define IRQ_DESC_ERR BIT(9) #define IRQ_RESP_ERR BIT(10) @@ -149,13 +139,12 @@ struct sd_emmc_desc { u32 cmd_data; u32 cmd_resp; }; -#define CMD_CFG_LENGTH_SHIFT 0 -#define CMD_CFG_LENGTH_MASK 0x1ff + +#define CMD_CFG_LENGTH_MASK GENMASK(8, 0) #define CMD_CFG_BLOCK_MODE BIT(9) #define CMD_CFG_R1B BIT(10) #define CMD_CFG_END_OF_CHAIN BIT(11) -#define CMD_CFG_TIMEOUT_SHIFT 12 -#define CMD_CFG_TIMEOUT_MASK 0xf +#define CMD_CFG_TIMEOUT_MASK GENMASK(15, 12) #define CMD_CFG_NO_RESP BIT(16) #define CMD_CFG_NO_CMD BIT(17) #define CMD_CFG_DATA_IO BIT(18) @@ -164,15 +153,14 @@ struct sd_emmc_desc { #define CMD_CFG_RESP_128 BIT(21) #define CMD_CFG_RESP_NUM BIT(22) #define CMD_CFG_DATA_NUM BIT(23) -#define CMD_CFG_CMD_INDEX_SHIFT 24 -#define CMD_CFG_CMD_INDEX_MASK 0x3f +#define CMD_CFG_CMD_INDEX_MASK GENMASK(29, 24) #define CMD_CFG_ERROR BIT(30) #define CMD_CFG_OWNER BIT(31) -#define CMD_DATA_MASK (~0x3) +#define CMD_DATA_MASK GENMASK(31, 2) #define CMD_DATA_BIG_ENDIAN BIT(1) #define CMD_DATA_SRAM BIT(0) -#define CMD_RESP_MASK (~0x1) +#define CMD_RESP_MASK GENMASK(31, 1) #define CMD_RESP_SRAM BIT(0) static int meson_mmc_clk_set(struct meson_host *host, unsigned long clk_rate) @@ -268,9 +256,8 @@ static int meson_mmc_clk_init(struct meson_host *host) init.flags = 0; init.parent_names = mux_parent_names; init.num_parents = MUX_CLK_NUM_PARENTS; - host->mux.reg = host->regs + SD_EMMC_CLOCK; - host->mux.shift = CLK_SRC_SHIFT; + host->mux.shift = __bf_shf(CLK_SRC_MASK); host->mux.mask = CLK_SRC_MASK; host->mux.flags = 0; host->mux.table = NULL; @@ -290,8 +277,8 @@ static int meson_mmc_clk_init(struct meson_host *host) init.num_parents = ARRAY_SIZE(clk_div_parents); host->cfg_div.reg = host->regs + SD_EMMC_CLOCK; - host->cfg_div.shift = CLK_DIV_SHIFT; - host->cfg_div.width = CLK_DIV_WIDTH; + host->cfg_div.shift = __bf_shf(CLK_DIV_MASK); + host->cfg_div.width = __builtin_popcountl(CLK_DIV_MASK); host->cfg_div.hw.init = &init; host->cfg_div.flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ROUND_CLOSEST | CLK_DIVIDER_ALLOW_ZERO; @@ -302,9 +289,9 @@ static int meson_mmc_clk_init(struct meson_host *host) /* init SD_EMMC_CLOCK to sane defaults w/min clock rate */ clk_reg = 0; - clk_reg |= CLK_PHASE_180 << CLK_PHASE_SHIFT; - clk_reg |= CLK_SRC_XTAL << CLK_SRC_SHIFT; - clk_reg |= CLK_DIV_MAX << CLK_DIV_SHIFT; + clk_reg |= FIELD_PREP(CLK_CORE_PHASE_MASK, CLK_PHASE_180); + clk_reg |= FIELD_PREP(CLK_SRC_MASK, CLK_SRC_XTAL); + clk_reg |= FIELD_PREP(CLK_DIV_MASK, CLK_DIV_MAX); clk_reg &= ~CLK_ALWAYS_ON; writel(clk_reg, host->regs + SD_EMMC_CLOCK); @@ -392,8 +379,8 @@ static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) val = readl(host->regs + SD_EMMC_CFG); orig = val; - val &= ~(CFG_BUS_WIDTH_MASK << CFG_BUS_WIDTH_SHIFT); - val |= bus_width << CFG_BUS_WIDTH_SHIFT; + val &= ~CFG_BUS_WIDTH_MASK; + val |= FIELD_PREP(CFG_BUS_WIDTH_MASK, bus_width); val &= ~CFG_DDR; if (ios->timing == MMC_TIMING_UHS_DDR50 || @@ -432,8 +419,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) /* Setup descriptors */ dma_rmb(); - cmd_cfg |= (cmd->opcode & CMD_CFG_CMD_INDEX_MASK) << - CMD_CFG_CMD_INDEX_SHIFT; + cmd_cfg |= FIELD_PREP(CMD_CFG_CMD_INDEX_MASK, cmd->opcode); cmd_cfg |= CMD_CFG_OWNER; /* owned by CPU */ /* Response */ @@ -454,30 +440,28 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) /* data? */ if (data) { cmd_cfg |= CMD_CFG_DATA_IO; - cmd_cfg |= ilog2(SD_EMMC_CMD_TIMEOUT_DATA) << - CMD_CFG_TIMEOUT_SHIFT; + cmd_cfg |= FIELD_PREP(CMD_CFG_TIMEOUT_MASK, + ilog2(SD_EMMC_CMD_TIMEOUT_DATA)); if (data->blocks > 1) { cmd_cfg |= CMD_CFG_BLOCK_MODE; - cmd_cfg |= (data->blocks & CMD_CFG_LENGTH_MASK) << - CMD_CFG_LENGTH_SHIFT; + cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, + data->blocks); /* check if block-size matches, if not update */ cfg = readl(host->regs + SD_EMMC_CFG); - blk_len = cfg & (CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT); - blk_len >>= CFG_BLK_LEN_SHIFT; + blk_len = FIELD_GET(CFG_BLK_LEN_MASK, cfg); if (blk_len != ilog2(data->blksz)) { dev_dbg(host->dev, "%s: update blk_len %d -> %d\n", __func__, blk_len, ilog2(data->blksz)); blk_len = ilog2(data->blksz); - cfg &= ~(CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT); - cfg |= blk_len << CFG_BLK_LEN_SHIFT; + cfg &= ~CFG_BLK_LEN_MASK; + cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, blk_len); writel(cfg, host->regs + SD_EMMC_CFG); } } else { - cmd_cfg |= (data->blksz & CMD_CFG_LENGTH_MASK) << - CMD_CFG_LENGTH_SHIFT; + cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, data->blksz); } data->bytes_xfered = 0; @@ -492,7 +476,8 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) cmd_data = host->bounce_dma_addr & CMD_DATA_MASK; } else { - cmd_cfg |= ilog2(SD_EMMC_CMD_TIMEOUT) << CMD_CFG_TIMEOUT_SHIFT; + cmd_cfg |= FIELD_PREP(CMD_CFG_TIMEOUT_MASK, + ilog2(SD_EMMC_CMD_TIMEOUT)); } host->cmd = cmd; @@ -664,9 +649,10 @@ static void meson_mmc_cfg_init(struct meson_host *host) { u32 cfg = 0; - cfg |= ilog2(SD_EMMC_CFG_RESP_TIMEOUT) << CFG_RESP_TIMEOUT_SHIFT; - cfg |= ilog2(SD_EMMC_CFG_CMD_GAP) << CFG_RC_CC_SHIFT; - cfg |= ilog2(SD_EMMC_CFG_BLK_SIZE) << CFG_BLK_LEN_SHIFT; + cfg |= FIELD_PREP(CFG_RESP_TIMEOUT_MASK, + ilog2(SD_EMMC_CFG_RESP_TIMEOUT)); + cfg |= FIELD_PREP(CFG_RC_CC_MASK, ilog2(SD_EMMC_CFG_CMD_GAP)); + cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, ilog2(SD_EMMC_CFG_BLK_SIZE)); writel(cfg, host->regs + SD_EMMC_CFG); } From f016c67629c818ca6a39f4d6e35d85d9e930eacc Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 25 Mar 2017 11:24:41 +0100 Subject: [PATCH 076/148] mmc: meson-gx: use per port interrupt names So far the driver name is used as interrupt description, therefore in /proc/interrupts it's not possible to tell which interrupt belongs to which port. Change this by switching to NULL what causes the default (device name) to be used. In our case that's the DT node name. Signed-off-by: Heiner Kallweit Reviewed-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index ab1f2d336ae2..0a0f5008940b 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -734,7 +734,7 @@ static int meson_mmc_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, irq, meson_mmc_irq, meson_mmc_irq_thread, IRQF_SHARED, - DRIVER_NAME, host); + NULL, host); if (ret) goto err_div_clk; From 4eee86c3d825109ed91555a553fbdb927592f009 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 25 Mar 2017 11:26:18 +0100 Subject: [PATCH 077/148] mmc: meson-gx: switch to dynamic timeout values Currently we use a fixed timeout of 4s for all data transfers. Switch to dynamic timeout values by making use of data->timeout_ns. Signed-off-by: Heiner Kallweit Reviewed-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 0a0f5008940b..810b68d5067b 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -163,6 +163,18 @@ struct sd_emmc_desc { #define CMD_RESP_MASK GENMASK(31, 1) #define CMD_RESP_SRAM BIT(0) +static unsigned int meson_mmc_get_timeout_msecs(struct mmc_data *data) +{ + unsigned int timeout = data->timeout_ns / NSEC_PER_MSEC; + + if (!timeout) + return SD_EMMC_CMD_TIMEOUT_DATA; + + timeout = roundup_pow_of_two(timeout); + + return min(timeout, 32768U); /* max. 2^15 ms */ +} + static int meson_mmc_clk_set(struct meson_host *host, unsigned long clk_rate) { struct mmc_host *mmc = host->mmc; @@ -441,7 +453,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) if (data) { cmd_cfg |= CMD_CFG_DATA_IO; cmd_cfg |= FIELD_PREP(CMD_CFG_TIMEOUT_MASK, - ilog2(SD_EMMC_CMD_TIMEOUT_DATA)); + ilog2(meson_mmc_get_timeout_msecs(data))); if (data->blocks > 1) { cmd_cfg |= CMD_CFG_BLOCK_MODE; From e5e4a3eb131f79af49f48a741807318c32f29427 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 25 Mar 2017 11:28:13 +0100 Subject: [PATCH 078/148] mmc: meson-gx: add CMD23 mode CMD23 mode (use "set block count" command before transferring multiple data blocks) typically is more performant as host / card know upfront how many data blocks to expect. Therefore add support for this mode to the driver. Signed-off-by: Heiner Kallweit Reviewed-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 810b68d5067b..f5dd5cc71040 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -175,6 +175,17 @@ static unsigned int meson_mmc_get_timeout_msecs(struct mmc_data *data) return min(timeout, 32768U); /* max. 2^15 ms */ } +static struct mmc_command *meson_mmc_get_next_command(struct mmc_command *cmd) +{ + if (cmd->opcode == MMC_SET_BLOCK_COUNT && !cmd->error) + return cmd->mrq->cmd; + else if (mmc_op_multi(cmd->opcode) && + (!cmd->mrq->sbc || cmd->error || cmd->data->error)) + return cmd->mrq->stop; + else + return NULL; +} + static int meson_mmc_clk_set(struct meson_host *host, unsigned long clk_rate) { struct mmc_host *mmc = host->mmc; @@ -620,7 +631,7 @@ out: static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) { struct meson_host *host = dev_id; - struct mmc_command *cmd = host->cmd; + struct mmc_command *next_cmd, *cmd = host->cmd; struct mmc_data *data; unsigned int xfer_bytes; @@ -635,10 +646,11 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) host->bounce_buf, xfer_bytes); } - if (!data || !data->stop || cmd->mrq->sbc) - meson_mmc_request_done(host->mmc, cmd->mrq); + next_cmd = meson_mmc_get_next_command(cmd); + if (next_cmd) + meson_mmc_start_cmd(host->mmc, next_cmd); else - meson_mmc_start_cmd(host->mmc, data->stop); + meson_mmc_request_done(host->mmc, cmd->mrq); return IRQ_HANDLED; } @@ -750,6 +762,7 @@ static int meson_mmc_probe(struct platform_device *pdev) if (ret) goto err_div_clk; + mmc->caps |= MMC_CAP_CMD23; mmc->max_blk_count = CMD_CFG_LENGTH_MASK; mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size; From 6335d68349a85382cc55a5260d5bfda85f8e24a8 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sun, 26 Mar 2017 20:41:32 +0200 Subject: [PATCH 079/148] mmc: core: add mmc_get_dma_dir Add function for determining DMA direction to core. Signed-off-by: Heiner Kallweit Reviewed-by: Shawn Lin Signed-off-by: Ulf Hansson --- include/linux/mmc/host.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 83f1c4a9f03b..21385ac0c9b1 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -17,6 +17,7 @@ #include #include #include +#include struct mmc_ios { unsigned int clock; /* clock rate */ @@ -499,6 +500,11 @@ static inline bool mmc_can_retune(struct mmc_host *host) return host->can_retune == 1; } +static inline enum dma_data_direction mmc_get_dma_dir(struct mmc_data *data) +{ + return data->flags & MMC_DATA_WRITE ? DMA_TO_DEVICE : DMA_FROM_DEVICE; +} + int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error); int mmc_abort_tuning(struct mmc_host *host, u32 opcode); From feeef096a770d288c943c1759785bd9d4f9247b7 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sun, 26 Mar 2017 20:45:56 +0200 Subject: [PATCH 080/148] mmc: use new core function mmc_get_dma_dir Use new core function mmc_get_dma_dir(). Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson --- drivers/mmc/host/android-goldfish.c | 10 ++-------- drivers/mmc/host/atmel-mci.c | 30 +++++++++++------------------ drivers/mmc/host/davinci_mmc.c | 14 ++++---------- drivers/mmc/host/dw_mmc.c | 14 +++----------- drivers/mmc/host/jz4740_mmc.c | 9 ++------- drivers/mmc/host/mmc_spi.c | 5 +---- drivers/mmc/host/mmci.c | 20 ++++++++----------- drivers/mmc/host/moxart-mmc.c | 8 +++----- drivers/mmc/host/mtk-sd.c | 8 ++------ drivers/mmc/host/mvsdio.c | 11 +++++------ drivers/mmc/host/omap_hsmmc.c | 17 ++++------------ drivers/mmc/host/s3cmci.c | 4 ++-- drivers/mmc/host/sdhci.c | 9 +++------ drivers/mmc/host/sunxi-mmc.c | 14 +++----------- 14 files changed, 53 insertions(+), 120 deletions(-) diff --git a/drivers/mmc/host/android-goldfish.c b/drivers/mmc/host/android-goldfish.c index 590a8a4522be..5b3e1c9bb75f 100644 --- a/drivers/mmc/host/android-goldfish.c +++ b/drivers/mmc/host/android-goldfish.c @@ -212,10 +212,7 @@ static void goldfish_mmc_xfer_done(struct goldfish_mmc_host *host, if (host->dma_in_use) { enum dma_data_direction dma_data_dir; - if (data->flags & MMC_DATA_WRITE) - dma_data_dir = DMA_TO_DEVICE; - else - dma_data_dir = DMA_FROM_DEVICE; + dma_data_dir = mmc_get_dma_dir(data); if (dma_data_dir == DMA_FROM_DEVICE) { /* @@ -390,10 +387,7 @@ static void goldfish_mmc_prepare_data(struct goldfish_mmc_host *host, */ sg_len = (data->blocks == 1) ? 1 : data->sg_len; - if (data->flags & MMC_DATA_WRITE) - dma_data_dir = DMA_TO_DEVICE; - else - dma_data_dir = DMA_FROM_DEVICE; + dma_data_dir = mmc_get_dma_dir(data); host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, sg_len, dma_data_dir); diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 0ad8ef565b74..388e4a3f13e6 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -954,8 +954,7 @@ static void atmci_pdc_cleanup(struct atmel_mci *host) if (data) dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len, - ((data->flags & MMC_DATA_WRITE) - ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); + mmc_get_dma_dir(data)); } /* @@ -993,8 +992,7 @@ static void atmci_dma_cleanup(struct atmel_mci *host) if (data) dma_unmap_sg(host->dma.chan->device->dev, data->sg, data->sg_len, - ((data->flags & MMC_DATA_WRITE) - ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); + mmc_get_dma_dir(data)); } /* @@ -1095,7 +1093,6 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data) { u32 iflags, tmp; unsigned int sg_len; - enum dma_data_direction dir; int i; data->error = -EINPROGRESS; @@ -1107,13 +1104,10 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data) /* Enable pdc mode */ atmci_writel(host, ATMCI_MR, host->mode_reg | ATMCI_MR_PDCMODE); - if (data->flags & MMC_DATA_READ) { - dir = DMA_FROM_DEVICE; + if (data->flags & MMC_DATA_READ) iflags |= ATMCI_ENDRX | ATMCI_RXBUFF; - } else { - dir = DMA_TO_DEVICE; + else iflags |= ATMCI_ENDTX | ATMCI_TXBUFE | ATMCI_BLKE; - } /* Set BLKLEN */ tmp = atmci_readl(host, ATMCI_MR); @@ -1123,7 +1117,8 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data) /* Configure PDC */ host->data_size = data->blocks * data->blksz; - sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, dir); + sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); if ((!host->caps.has_rwproof) && (host->data->flags & MMC_DATA_WRITE)) { @@ -1135,9 +1130,8 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data) } if (host->data_size) - atmci_pdc_set_both_buf(host, - ((dir == DMA_FROM_DEVICE) ? XFER_RECEIVE : XFER_TRANSMIT)); - + atmci_pdc_set_both_buf(host, data->flags & MMC_DATA_READ ? + XFER_RECEIVE : XFER_TRANSMIT); return iflags; } @@ -1148,7 +1142,6 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) struct dma_async_tx_descriptor *desc; struct scatterlist *sg; unsigned int i; - enum dma_data_direction direction; enum dma_transfer_direction slave_dirn; unsigned int sglen; u32 maxburst; @@ -1186,12 +1179,10 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) return -ENODEV; if (data->flags & MMC_DATA_READ) { - direction = DMA_FROM_DEVICE; host->dma_conf.direction = slave_dirn = DMA_DEV_TO_MEM; maxburst = atmci_convert_chksize(host, host->dma_conf.src_maxburst); } else { - direction = DMA_TO_DEVICE; host->dma_conf.direction = slave_dirn = DMA_MEM_TO_DEV; maxburst = atmci_convert_chksize(host, host->dma_conf.dst_maxburst); @@ -1202,7 +1193,7 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) ATMCI_DMAEN); sglen = dma_map_sg(chan->device->dev, data->sg, - data->sg_len, direction); + data->sg_len, mmc_get_dma_dir(data)); dmaengine_slave_config(chan, &host->dma_conf); desc = dmaengine_prep_slave_sg(chan, @@ -1217,7 +1208,8 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) return iflags; unmap_exit: - dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, direction); + dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); return -ENOMEM; } diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c index 1e2600da105f..621ce47e0e4a 100644 --- a/drivers/mmc/host/davinci_mmc.c +++ b/drivers/mmc/host/davinci_mmc.c @@ -478,18 +478,14 @@ static int mmc_davinci_start_dma_transfer(struct mmc_davinci_host *host, int ret = 0; host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - ((data->flags & MMC_DATA_WRITE) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE)); + mmc_get_dma_dir(data)); /* no individual DMA segment should need a partial FIFO */ for (i = 0; i < host->sg_len; i++) { if (sg_dma_len(data->sg + i) & mask) { dma_unmap_sg(mmc_dev(host->mmc), - data->sg, data->sg_len, - (data->flags & MMC_DATA_WRITE) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE); + data->sg, data->sg_len, + mmc_get_dma_dir(data)); return -1; } } @@ -802,9 +798,7 @@ mmc_davinci_xfer_done(struct mmc_davinci_host *host, struct mmc_data *data) davinci_abort_dma(host); dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - (data->flags & MMC_DATA_WRITE) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE); + mmc_get_dma_dir(data)); host->do_dma = false; } host->data_dir = DAVINCI_MMC_DATADIR_NONE; diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index d0f0f658e741..e45129f48174 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -432,14 +432,6 @@ static void dw_mci_stop_dma(struct dw_mci *host) set_bit(EVENT_XFER_COMPLETE, &host->pending_events); } -static int dw_mci_get_dma_dir(struct mmc_data *data) -{ - if (data->flags & MMC_DATA_WRITE) - return DMA_TO_DEVICE; - else - return DMA_FROM_DEVICE; -} - static void dw_mci_dma_cleanup(struct dw_mci *host) { struct mmc_data *data = host->data; @@ -448,7 +440,7 @@ static void dw_mci_dma_cleanup(struct dw_mci *host) dma_unmap_sg(host->dev, data->sg, data->sg_len, - dw_mci_get_dma_dir(data)); + mmc_get_dma_dir(data)); data->host_cookie = COOKIE_UNMAPPED; } } @@ -904,7 +896,7 @@ static int dw_mci_pre_dma_transfer(struct dw_mci *host, sg_len = dma_map_sg(host->dev, data->sg, data->sg_len, - dw_mci_get_dma_dir(data)); + mmc_get_dma_dir(data)); if (sg_len == 0) return -EINVAL; @@ -944,7 +936,7 @@ static void dw_mci_post_req(struct mmc_host *mmc, dma_unmap_sg(slot->host->dev, data->sg, data->sg_len, - dw_mci_get_dma_dir(data)); + mmc_get_dma_dir(data)); data->host_cookie = COOKIE_UNMAPPED; } diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c index 819ad32964fc..57e254aac48d 100644 --- a/drivers/mmc/host/jz4740_mmc.c +++ b/drivers/mmc/host/jz4740_mmc.c @@ -200,11 +200,6 @@ free_master_write: return -ENODEV; } -static inline int jz4740_mmc_get_dma_dir(struct mmc_data *data) -{ - return (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; -} - static inline struct dma_chan *jz4740_mmc_get_dma_chan(struct jz4740_mmc_host *host, struct mmc_data *data) { @@ -215,7 +210,7 @@ static void jz4740_mmc_dma_unmap(struct jz4740_mmc_host *host, struct mmc_data *data) { struct dma_chan *chan = jz4740_mmc_get_dma_chan(host, data); - enum dma_data_direction dir = jz4740_mmc_get_dma_dir(data); + enum dma_data_direction dir = mmc_get_dma_dir(data); dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); } @@ -227,7 +222,7 @@ static int jz4740_mmc_prepare_dma_data(struct jz4740_mmc_host *host, struct dma_chan *chan) { struct jz4740_mmc_host_next *next_data = &host->next_data; - enum dma_data_direction dir = jz4740_mmc_get_dma_dir(data); + enum dma_data_direction dir = mmc_get_dma_dir(data); int sg_len; if (!next && data->host_cookie && diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c index e77d79c8cd9f..476e53d30128 100644 --- a/drivers/mmc/host/mmc_spi.c +++ b/drivers/mmc/host/mmc_spi.c @@ -888,10 +888,7 @@ mmc_spi_data_do(struct mmc_spi_host *host, struct mmc_command *cmd, u32 clock_rate; unsigned long timeout; - if (data->flags & MMC_DATA_READ) - direction = DMA_FROM_DEVICE; - else - direction = DMA_TO_DEVICE; + direction = mmc_get_dma_dir(data); mmc_spi_setup_data_message(host, multiple, direction); t = &host->t; diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 0c6420bb2f00..d1ca2f489054 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -516,17 +516,14 @@ static void mmci_dma_data_error(struct mmci_host *host) static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) { struct dma_chan *chan; - enum dma_data_direction dir; - if (data->flags & MMC_DATA_READ) { - dir = DMA_FROM_DEVICE; + if (data->flags & MMC_DATA_READ) chan = host->dma_rx_channel; - } else { - dir = DMA_TO_DEVICE; + else chan = host->dma_tx_channel; - } - dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); + dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); } static void mmci_dma_finalize(struct mmci_host *host, struct mmc_data *data) @@ -589,17 +586,14 @@ static int __mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, struct dma_chan *chan; struct dma_device *device; struct dma_async_tx_descriptor *desc; - enum dma_data_direction buffer_dirn; int nr_sg; unsigned long flags = DMA_CTRL_ACK; if (data->flags & MMC_DATA_READ) { conf.direction = DMA_DEV_TO_MEM; - buffer_dirn = DMA_FROM_DEVICE; chan = host->dma_rx_channel; } else { conf.direction = DMA_MEM_TO_DEV; - buffer_dirn = DMA_TO_DEVICE; chan = host->dma_tx_channel; } @@ -612,7 +606,8 @@ static int __mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, return -EINVAL; device = chan->device; - nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, buffer_dirn); + nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); if (nr_sg == 0) return -EINVAL; @@ -631,7 +626,8 @@ static int __mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, return 0; unmap_exit: - dma_unmap_sg(device->dev, data->sg, data->sg_len, buffer_dirn); + dma_unmap_sg(device->dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); return -ENOMEM; } diff --git a/drivers/mmc/host/moxart-mmc.c b/drivers/mmc/host/moxart-mmc.c index bbad309679cf..d4dc55ac7dea 100644 --- a/drivers/mmc/host/moxart-mmc.c +++ b/drivers/mmc/host/moxart-mmc.c @@ -256,7 +256,7 @@ static void moxart_dma_complete(void *param) static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host) { - u32 len, dir_data, dir_slave; + u32 len, dir_slave; long dma_time; struct dma_async_tx_descriptor *desc = NULL; struct dma_chan *dma_chan; @@ -266,16 +266,14 @@ static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host) if (data->flags & MMC_DATA_WRITE) { dma_chan = host->dma_chan_tx; - dir_data = DMA_TO_DEVICE; dir_slave = DMA_MEM_TO_DEV; } else { dma_chan = host->dma_chan_rx; - dir_data = DMA_FROM_DEVICE; dir_slave = DMA_DEV_TO_MEM; } len = dma_map_sg(dma_chan->device->dev, data->sg, - data->sg_len, dir_data); + data->sg_len, mmc_get_dma_dir(data)); if (len > 0) { desc = dmaengine_prep_slave_sg(dma_chan, data->sg, @@ -301,7 +299,7 @@ static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host) dma_unmap_sg(dma_chan->device->dev, data->sg, data->sg_len, - dir_data); + mmc_get_dma_dir(data)); } diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index 0789d58e803c..5c1e178fc5f9 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -474,11 +474,9 @@ static void msdc_prepare_data(struct msdc_host *host, struct mmc_request *mrq) struct mmc_data *data = mrq->data; if (!(data->host_cookie & MSDC_PREPARE_FLAG)) { - bool read = (data->flags & MMC_DATA_READ) != 0; - data->host_cookie |= MSDC_PREPARE_FLAG; data->sg_count = dma_map_sg(host->dev, data->sg, data->sg_len, - read ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + mmc_get_dma_dir(data)); } } @@ -490,10 +488,8 @@ static void msdc_unprepare_data(struct msdc_host *host, struct mmc_request *mrq) return; if (data->host_cookie & MSDC_PREPARE_FLAG) { - bool read = (data->flags & MMC_DATA_READ) != 0; - dma_unmap_sg(host->dev, data->sg, data->sg_len, - read ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + mmc_get_dma_dir(data)); data->host_cookie &= ~MSDC_PREPARE_FLAG; } } diff --git a/drivers/mmc/host/mvsdio.c b/drivers/mmc/host/mvsdio.c index 42296e55b9de..58d74b8d6c79 100644 --- a/drivers/mmc/host/mvsdio.c +++ b/drivers/mmc/host/mvsdio.c @@ -125,10 +125,10 @@ static int mvsd_setup_data(struct mvsd_host *host, struct mmc_data *data) return 1; } else { dma_addr_t phys_addr; - int dma_dir = (data->flags & MMC_DATA_READ) ? - DMA_FROM_DEVICE : DMA_TO_DEVICE; - host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, dma_dir); + + host->sg_frags = dma_map_sg(mmc_dev(host->mmc), + data->sg, data->sg_len, + mmc_get_dma_dir(data)); phys_addr = sg_dma_address(data->sg); mvsd_write(MVSD_SYS_ADDR_LOW, (u32)phys_addr & 0xffff); mvsd_write(MVSD_SYS_ADDR_HI, (u32)phys_addr >> 16); @@ -294,8 +294,7 @@ static u32 mvsd_finish_data(struct mvsd_host *host, struct mmc_data *data, host->pio_size = 0; } else { dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_frags, - (data->flags & MMC_DATA_READ) ? - DMA_FROM_DEVICE : DMA_TO_DEVICE); + mmc_get_dma_dir(data)); } if (err_status & MVSD_ERR_DATA_TIMEOUT) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index a58bd653ed8b..bf64cf23aa59 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -935,15 +935,6 @@ omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd, OMAP_HSMMC_WRITE(host->base, CMD, cmdreg); } -static int -omap_hsmmc_get_dma_dir(struct omap_hsmmc_host *host, struct mmc_data *data) -{ - if (data->flags & MMC_DATA_WRITE) - return DMA_TO_DEVICE; - else - return DMA_FROM_DEVICE; -} - static struct dma_chan *omap_hsmmc_get_dma_chan(struct omap_hsmmc_host *host, struct mmc_data *data) { @@ -1055,7 +1046,7 @@ static void omap_hsmmc_dma_cleanup(struct omap_hsmmc_host *host, int errno) dmaengine_terminate_all(chan); dma_unmap_sg(chan->device->dev, host->data->sg, host->data->sg_len, - omap_hsmmc_get_dma_dir(host, host->data)); + mmc_get_dma_dir(host->data)); host->data->host_cookie = 0; } @@ -1350,7 +1341,7 @@ static void omap_hsmmc_dma_callback(void *param) if (!data->host_cookie) dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, - omap_hsmmc_get_dma_dir(host, data)); + mmc_get_dma_dir(data)); req_in_progress = host->req_in_progress; host->dma_ch = -1; @@ -1383,7 +1374,7 @@ static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host, /* Check if next job is already prepared */ if (next || data->host_cookie != host->next_data.cookie) { dma_len = dma_map_sg(chan->device->dev, data->sg, data->sg_len, - omap_hsmmc_get_dma_dir(host, data)); + mmc_get_dma_dir(data)); } else { dma_len = host->next_data.dma_len; @@ -1569,7 +1560,7 @@ static void omap_hsmmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq, struct dma_chan *c = omap_hsmmc_get_dma_chan(host, data); dma_unmap_sg(c->device->dev, data->sg, data->sg_len, - omap_hsmmc_get_dma_dir(host, data)); + mmc_get_dma_dir(data)); data->host_cookie = 0; } } diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c index 7a173f8c455b..5c9037ebc44e 100644 --- a/drivers/mmc/host/s3cmci.c +++ b/drivers/mmc/host/s3cmci.c @@ -1104,7 +1104,7 @@ static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data) conf.direction = DMA_MEM_TO_DEV; dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mmc_get_dma_dir(data)); dmaengine_slave_config(host->dma, &conf); desc = dmaengine_prep_slave_sg(host->dma, data->sg, data->sg_len, @@ -1121,7 +1121,7 @@ static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data) unmap_exit: dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mmc_get_dma_dir(data)); return -ENOMEM; } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 147df8bcc231..a1a38e04a9a5 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -502,8 +502,7 @@ static int sdhci_pre_dma_transfer(struct sdhci_host *host, return data->sg_count; sg_count = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - data->flags & MMC_DATA_WRITE ? - DMA_TO_DEVICE : DMA_FROM_DEVICE); + mmc_get_dma_dir(data)); if (sg_count == 0) return -ENOSPC; @@ -2219,8 +2218,7 @@ static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq, if (data->host_cookie != COOKIE_UNMAPPED) dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - data->flags & MMC_DATA_WRITE ? - DMA_TO_DEVICE : DMA_FROM_DEVICE); + mmc_get_dma_dir(data)); data->host_cookie = COOKIE_UNMAPPED; } @@ -2336,8 +2334,7 @@ static bool sdhci_request_done(struct sdhci_host *host) if (data && data->host_cookie == COOKIE_MAPPED) { dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - (data->flags & MMC_DATA_READ) ? - DMA_FROM_DEVICE : DMA_TO_DEVICE); + mmc_get_dma_dir(data)); data->host_cookie = COOKIE_UNMAPPED; } } diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c index 0391c62cc99f..d6fa2214aaae 100644 --- a/drivers/mmc/host/sunxi-mmc.c +++ b/drivers/mmc/host/sunxi-mmc.c @@ -385,14 +385,6 @@ static void sunxi_mmc_init_idma_des(struct sunxi_mmc_host *host, wmb(); } -static enum dma_data_direction sunxi_mmc_get_dma_dir(struct mmc_data *data) -{ - if (data->flags & MMC_DATA_WRITE) - return DMA_TO_DEVICE; - else - return DMA_FROM_DEVICE; -} - static int sunxi_mmc_map_dma(struct sunxi_mmc_host *host, struct mmc_data *data) { @@ -400,7 +392,7 @@ static int sunxi_mmc_map_dma(struct sunxi_mmc_host *host, struct scatterlist *sg; dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - sunxi_mmc_get_dma_dir(data)); + mmc_get_dma_dir(data)); if (dma_len == 0) { dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n"); return -ENOMEM; @@ -551,7 +543,7 @@ static irqreturn_t sunxi_mmc_finalize_request(struct sunxi_mmc_host *host) rval |= SDXC_FIFO_RESET; mmc_writel(host, REG_GCTRL, rval); dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - sunxi_mmc_get_dma_dir(data)); + mmc_get_dma_dir(data)); } mmc_writel(host, REG_RINTR, 0xffff); @@ -1022,7 +1014,7 @@ static void sunxi_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) if (data) dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len, - sunxi_mmc_get_dma_dir(data)); + mmc_get_dma_dir(data)); dev_err(mmc_dev(mmc), "request already pending\n"); mrq->cmd->error = -EBUSY; From bf3240bada0211b4a555d75f027181c8432b2d20 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Sat, 25 Mar 2017 13:17:00 +0000 Subject: [PATCH 081/148] mmc: bcm2835: Fix possible NULL ptr dereference in bcm2835_request This fixes a NULL pointer dereference in case of a MMC request with a set block count command and no data. Reported-by: Dan Carpenter Signed-off-by: Stefan Wahren Tested-by: Peter Robinson Reviewed-by: Jaehoon Chung Signed-off-by: Ulf Hansson --- drivers/mmc/host/bcm2835.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c index 7d1b0db7fbb7..1f343a477b3d 100644 --- a/drivers/mmc/host/bcm2835.c +++ b/drivers/mmc/host/bcm2835.c @@ -1200,7 +1200,8 @@ static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } - host->use_sbc = !!mrq->sbc && (host->mrq->data->flags & MMC_DATA_READ); + host->use_sbc = !!mrq->sbc && host->mrq->data && + (host->mrq->data->flags & MMC_DATA_READ); if (host->use_sbc) { if (bcm2835_send_command(host, mrq->sbc)) { if (!host->use_busy) From a0f8243229ed071c8da0ea7cedc1b7bf1b1515da Mon Sep 17 00:00:00 2001 From: Piotr Sroka Date: Tue, 21 Mar 2017 14:32:16 +0000 Subject: [PATCH 082/148] mmc: sdhci-cadence: Fix writing PHY delay Add polling for ACK to be sure that data are written to PHY register. Signed-off-by: Piotr Sroka Reviewed-by: Masahiro Yamada Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 07a27dcb42be..84bdcc59c024 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -68,11 +68,12 @@ struct sdhci_cdns_priv { bool enhanced_strobe; }; -static void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, - u8 addr, u8 data) +static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, + u8 addr, u8 data) { void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; u32 tmp; + int ret; tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) | (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT); @@ -81,8 +82,14 @@ static void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, tmp |= SDHCI_CDNS_HRS04_WR; writel(tmp, reg); + ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 0, 10); + if (ret) + return ret; + tmp &= ~SDHCI_CDNS_HRS04_WR; writel(tmp, reg); + + return 0; } static void sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) From 6f4b0919020a482e41af4e715efaa79f45f6cde6 Mon Sep 17 00:00:00 2001 From: Piotr Sroka Date: Tue, 21 Mar 2017 14:33:01 +0000 Subject: [PATCH 083/148] dt-bindings: mmc: add description of PHY delays for sdhci-cadence DTS properties are used instead of fixed data because PHY settings can be different for different chips/boards. Add description of new DLL PHY delays. Signed-off-by: Piotr Sroka Reviewed-by: Masahiro Yamada Acked-by: Rob Herring Signed-off-by: Ulf Hansson --- .../devicetree/bindings/mmc/sdhci-cadence.txt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt index c0f37cb41a9b..fa423c277853 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt @@ -19,6 +19,53 @@ if supported. See mmc.txt for details. - mmc-hs400-1_8v - mmc-hs400-1_2v +Some PHY delays can be configured by following properties. +PHY DLL input delays: +They are used to delay the data valid window, and align the window +to sampling clock. The delay starts from 5ns (for delay parameter equal to 0) +and it is increased by 2.5ns in each step. +- cdns,phy-input-delay-sd-highspeed: + Value of the delay in the input path for SD high-speed timing + Valid range = [0:0x1F]. +- cdns,phy-input-delay-legacy: + Value of the delay in the input path for legacy timing + Valid range = [0:0x1F]. +- cdns,phy-input-delay-sd-uhs-sdr12: + Value of the delay in the input path for SD UHS SDR12 timing + Valid range = [0:0x1F]. +- cdns,phy-input-delay-sd-uhs-sdr25: + Value of the delay in the input path for SD UHS SDR25 timing + Valid range = [0:0x1F]. +- cdns,phy-input-delay-sd-uhs-sdr50: + Value of the delay in the input path for SD UHS SDR50 timing + Valid range = [0:0x1F]. +- cdns,phy-input-delay-sd-uhs-ddr50: + Value of the delay in the input path for SD UHS DDR50 timing + Valid range = [0:0x1F]. +- cdns,phy-input-delay-mmc-highspeed: + Value of the delay in the input path for MMC high-speed timing + Valid range = [0:0x1F]. +- cdns,phy-input-delay-mmc-ddr: + Value of the delay in the input path for eMMC high-speed DDR timing + Valid range = [0:0x1F]. + +PHY DLL clock delays: +Each delay property represents the fraction of the clock period. +The approximate delay value will be +(/128)*sdmclk_clock_period. +- cdns,phy-dll-delay-sdclk: + Value of the delay introduced on the sdclk output + for all modes except HS200, HS400 and HS400_ES. + Valid range = [0:0x7F]. +- cdns,phy-dll-delay-sdclk-hsmmc: + Value of the delay introduced on the sdclk output + for HS200, HS400 and HS400_ES speed modes. + Valid range = [0:0x7F]. +- cdns,phy-dll-delay-strobe: + Value of the delay introduced on the dat_strobe input + used in HS400 / HS400_ES speed modes. + Valid range = [0:0x7F]. + Example: emmc: sdhci@5a000000 { compatible = "socionext,uniphier-sd4hc", "cdns,sd4hc"; @@ -29,4 +76,5 @@ Example: mmc-ddr-1_8v; mmc-hs200-1_8v; mmc-hs400-1_8v; + cdns,phy-dll-delay-sdclk = <0>; }; From a89c472d8b55c5afc4c79e6e3d1338730034eb01 Mon Sep 17 00:00:00 2001 From: Piotr Sroka Date: Tue, 21 Mar 2017 14:33:11 +0000 Subject: [PATCH 084/148] mmc: sdhci-cadence: Update PHY delay configuration DTS properties are used instead of fixed data because PHY settings can be different for different chips/boards. Signed-off-by: Piotr Sroka Reviewed-by: Masahiro Yamada Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 53 +++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 84bdcc59c024..3a5f3f026b60 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "sdhci-pltfm.h" @@ -55,6 +56,9 @@ #define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 #define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 #define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 +#define SDHCI_CDNS_PHY_DLY_SDCLK 0x0b +#define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c +#define SDHCI_CDNS_PHY_DLY_STROBE 0x0d /* * The tuned val register is 6 bit-wide, but not the whole of the range is @@ -68,6 +72,25 @@ struct sdhci_cdns_priv { bool enhanced_strobe; }; +struct sdhci_cdns_phy_cfg { + const char *property; + u8 addr; +}; + +static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { + { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, + { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, + { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, + { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, + { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, + { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, + { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, + { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, + { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, + { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, + { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, +}; + static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, u8 addr, u8 data) { @@ -92,13 +115,26 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, return 0; } -static void sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) +static int sdhci_cdns_phy_init(struct device_node *np, + struct sdhci_cdns_priv *priv) { - sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_HS, 4); - sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_DEFAULT, 4); - sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_LEGACY, 9); - sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_SDR, 2); - sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_DDR, 3); + u32 val; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { + ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property, + &val); + if (ret) + continue; + + ret = sdhci_cdns_write_phy_reg(priv, + sdhci_cdns_phy_cfgs[i].addr, + val); + if (ret) + return ret; + } + + return 0; } static inline void *sdhci_cdns_priv(struct sdhci_host *host) @@ -267,6 +303,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev) struct sdhci_cdns_priv *priv; struct clk *clk; int ret; + struct device *dev = &pdev->dev; clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(clk)) @@ -297,7 +334,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev) if (ret) goto free; - sdhci_cdns_phy_init(priv); + ret = sdhci_cdns_phy_init(dev->of_node, priv); + if (ret) + goto free; ret = sdhci_add_host(host); if (ret) From edf985790659503eedd5736ba89ad7e9fd754cf0 Mon Sep 17 00:00:00 2001 From: Piotr Sroka Date: Tue, 21 Mar 2017 14:33:26 +0000 Subject: [PATCH 085/148] mmc: sdhci-cadence: refactor probe function Use added dev variable for devm_clk_get. Signed-off-by: Piotr Sroka Reviewed-by: Masahiro Yamada Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 3a5f3f026b60..7797e98a7541 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -305,7 +305,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev) int ret; struct device *dev = &pdev->dev; - clk = devm_clk_get(&pdev->dev, NULL); + clk = devm_clk_get(dev, NULL); if (IS_ERR(clk)) return PTR_ERR(clk); From 3d03f6a99a8de552e094445dde341ca4b4d4326a Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Mon, 27 Mar 2017 21:57:11 +0200 Subject: [PATCH 086/148] mmc: meson-gx: factor out setting block size Factor out setting the block size. This also contributes to reducing complexity of function meson_mmc_start_cmd. Signed-off-by: Heiner Kallweit Reviewed-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 42 +++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index f5dd5cc71040..0de320084c45 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -431,12 +431,36 @@ static void meson_mmc_request_done(struct mmc_host *mmc, mmc_request_done(host->mmc, mrq); } +static void meson_mmc_set_blksz(struct mmc_host *mmc, unsigned int blksz) +{ + struct meson_host *host = mmc_priv(mmc); + u32 cfg, blksz_old; + + cfg = readl(host->regs + SD_EMMC_CFG); + blksz_old = FIELD_GET(CFG_BLK_LEN_MASK, cfg); + + if (!is_power_of_2(blksz)) + dev_err(host->dev, "blksz %u is not a power of 2\n", blksz); + + blksz = ilog2(blksz); + + /* check if block-size matches, if not update */ + if (blksz == blksz_old) + return; + + dev_dbg(host->dev, "%s: update blk_len %d -> %d\n", __func__, + blksz_old, blksz); + + cfg &= ~CFG_BLK_LEN_MASK; + cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, blksz); + writel(cfg, host->regs + SD_EMMC_CFG); +} + static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) { struct meson_host *host = mmc_priv(mmc); struct mmc_data *data = cmd->data; - u32 cfg, cmd_cfg = 0, cmd_data = 0; - u8 blk_len; + u32 cmd_cfg = 0, cmd_data = 0; unsigned int xfer_bytes = 0; /* Setup descriptors */ @@ -470,19 +494,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) cmd_cfg |= CMD_CFG_BLOCK_MODE; cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, data->blocks); - - /* check if block-size matches, if not update */ - cfg = readl(host->regs + SD_EMMC_CFG); - blk_len = FIELD_GET(CFG_BLK_LEN_MASK, cfg); - if (blk_len != ilog2(data->blksz)) { - dev_dbg(host->dev, "%s: update blk_len %d -> %d\n", - __func__, blk_len, - ilog2(data->blksz)); - blk_len = ilog2(data->blksz); - cfg &= ~CFG_BLK_LEN_MASK; - cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, blk_len); - writel(cfg, host->regs + SD_EMMC_CFG); - } + meson_mmc_set_blksz(mmc, data->blksz); } else { cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, data->blksz); } From 75c7fd96d0e51e8d652767e910a79aad67fe9826 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Mon, 27 Mar 2017 22:02:32 +0200 Subject: [PATCH 087/148] mmc: meson-gx: factor out setting response configuration bits Factor out setting the response configuration bits thus further reducing complexity of function meson_mmc_start_cmd. Signed-off-by: Heiner Kallweit Reviewed-by: Kevin Hilman Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 0de320084c45..0036680bad9e 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -456,6 +456,23 @@ static void meson_mmc_set_blksz(struct mmc_host *mmc, unsigned int blksz) writel(cfg, host->regs + SD_EMMC_CFG); } +static void meson_mmc_set_response_bits(struct mmc_command *cmd, u32 *cmd_cfg) +{ + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) + *cmd_cfg |= CMD_CFG_RESP_128; + *cmd_cfg |= CMD_CFG_RESP_NUM; + + if (!(cmd->flags & MMC_RSP_CRC)) + *cmd_cfg |= CMD_CFG_RESP_NOCRC; + + if (cmd->flags & MMC_RSP_BUSY) + *cmd_cfg |= CMD_CFG_R1B; + } else { + *cmd_cfg |= CMD_CFG_NO_RESP; + } +} + static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) { struct meson_host *host = mmc_priv(mmc); @@ -469,20 +486,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) cmd_cfg |= FIELD_PREP(CMD_CFG_CMD_INDEX_MASK, cmd->opcode); cmd_cfg |= CMD_CFG_OWNER; /* owned by CPU */ - /* Response */ - if (cmd->flags & MMC_RSP_PRESENT) { - if (cmd->flags & MMC_RSP_136) - cmd_cfg |= CMD_CFG_RESP_128; - cmd_cfg |= CMD_CFG_RESP_NUM; - - if (!(cmd->flags & MMC_RSP_CRC)) - cmd_cfg |= CMD_CFG_RESP_NOCRC; - - if (cmd->flags & MMC_RSP_BUSY) - cmd_cfg |= CMD_CFG_R1B; - } else { - cmd_cfg |= CMD_CFG_NO_RESP; - } + meson_mmc_set_response_bits(cmd, &cmd_cfg); /* data? */ if (data) { From d3e974074f66a258c9fea12defb9ce1914ef4e4c Mon Sep 17 00:00:00 2001 From: Azhar Shaikh Date: Wed, 29 Mar 2017 11:16:32 -0700 Subject: [PATCH 088/148] mmc: sdhci-acpi: Set MMC_CAP_AGGRESSIVE_PM for BYT-related Intel controllers Set MMC_CAP_AGGRESSIVE_PM for BYT-related Intel SD card controllers. Signed-off-by: Azhar Shaikh Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-acpi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-acpi.c b/drivers/mmc/host/sdhci-acpi.c index 665c5f3009b4..c6a9a1bfaa22 100644 --- a/drivers/mmc/host/sdhci-acpi.c +++ b/drivers/mmc/host/sdhci-acpi.c @@ -263,10 +263,8 @@ static int sdhci_acpi_sd_probe_slot(struct platform_device *pdev, /* Platform specific code during sd probe slot goes here */ - if (hid && !strcmp(hid, "80865ACA")) { + if (hid && !strcmp(hid, "80865ACA")) host->mmc_host_ops.get_cd = bxt_get_cd; - host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM; - } return 0; } @@ -302,7 +300,7 @@ static const struct sdhci_acpi_slot sdhci_acpi_slot_int_sd = { .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, .quirks2 = SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON | SDHCI_QUIRK2_STOP_WITH_TC, - .caps = MMC_CAP_WAIT_WHILE_BUSY, + .caps = MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_AGGRESSIVE_PM, .probe_slot = sdhci_acpi_sd_probe_slot, }; From c2c49a2e2bf3758ac429c5ac9941a855b75b5e22 Mon Sep 17 00:00:00 2001 From: Azhar Shaikh Date: Wed, 29 Mar 2017 11:16:33 -0700 Subject: [PATCH 089/148] mmc: sdhci-pci: Set MMC_CAP_AGGRESSIVE_PM for BYT-related Intel controllers Set MMC_CAP_AGGRESSIVE_PM for BYT-related Intel SD card controllers. Signed-off-by: Azhar Shaikh Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pci-core.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 6c0440ef54be..833072b8453e 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -630,16 +630,15 @@ static int byt_sdio_probe_slot(struct sdhci_pci_slot *slot) static int byt_sd_probe_slot(struct sdhci_pci_slot *slot) { byt_read_dsm(slot); - slot->host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; + slot->host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | + MMC_CAP_AGGRESSIVE_PM; slot->cd_idx = 0; slot->cd_override_level = true; if (slot->chip->pdev->device == PCI_DEVICE_ID_INTEL_BXT_SD || slot->chip->pdev->device == PCI_DEVICE_ID_INTEL_BXTM_SD || slot->chip->pdev->device == PCI_DEVICE_ID_INTEL_APL_SD || - slot->chip->pdev->device == PCI_DEVICE_ID_INTEL_GLK_SD) { + slot->chip->pdev->device == PCI_DEVICE_ID_INTEL_GLK_SD) slot->host->mmc_host_ops.get_cd = bxt_get_cd; - slot->host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM; - } return 0; } From d5cc7c80bafca02e9150a2af5ccab1aa0aa8be20 Mon Sep 17 00:00:00 2001 From: Sergio Prado Date: Fri, 31 Mar 2017 20:50:55 -0300 Subject: [PATCH 090/148] dt-bindings: mmc: add DT binding for S3C24XX MMC/SD/SDIO controller Adds the device tree bindings description for Samsung S3C24XX MMC/SD/SDIO controller, used as a connectivity interface with external MMC, SD and SDIO storage mediums. Acked-by: Rob Herring Signed-off-by: Sergio Prado Signed-off-by: Ulf Hansson --- .../bindings/mmc/samsung,s3cmci.txt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/samsung,s3cmci.txt diff --git a/Documentation/devicetree/bindings/mmc/samsung,s3cmci.txt b/Documentation/devicetree/bindings/mmc/samsung,s3cmci.txt new file mode 100644 index 000000000000..5f68feb9f9d6 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/samsung,s3cmci.txt @@ -0,0 +1,42 @@ +* Samsung's S3C24XX MMC/SD/SDIO controller device tree bindings + +Samsung's S3C24XX MMC/SD/SDIO controller is used as a connectivity interface +with external MMC, SD and SDIO storage mediums. + +This file documents differences between the core mmc properties described by +mmc.txt and the properties used by the Samsung S3C24XX MMC/SD/SDIO controller +implementation. + +Required SoC Specific Properties: +- compatible: should be one of the following + - "samsung,s3c2410-sdi": for controllers compatible with s3c2410 + - "samsung,s3c2412-sdi": for controllers compatible with s3c2412 + - "samsung,s3c2440-sdi": for controllers compatible with s3c2440 +- reg: register location and length +- interrupts: mmc controller interrupt +- clocks: Should reference the controller clock +- clock-names: Should contain "sdi" + +Required Board Specific Properties: +- pinctrl-0: Should specify pin control groups used for this controller. +- pinctrl-names: Should contain only one value - "default". + +Optional Properties: +- bus-width: number of data lines (see mmc.txt) +- cd-gpios: gpio for card detection (see mmc.txt) +- wp-gpios: gpio for write protection (see mmc.txt) + +Example: + + mmc0: mmc@5a000000 { + compatible = "samsung,s3c2440-sdi"; + pinctrl-names = "default"; + pinctrl-0 = <&sdi_pins>; + reg = <0x5a000000 0x100000>; + interrupts = <0 0 21 3>; + clocks = <&clocks PCLK_SDI>; + clock-names = "sdi"; + bus-width = <4>; + cd-gpios = <&gpg 8 GPIO_ACTIVE_LOW>; + wp-gpios = <&gph 8 GPIO_ACTIVE_LOW>; + }; From e584e07502131fb13e5879f3f2077c29377b08eb Mon Sep 17 00:00:00 2001 From: Sergio Prado Date: Fri, 31 Mar 2017 20:50:56 -0300 Subject: [PATCH 091/148] mmc: host: s3cmci: allow probing from device tree Allows configuring Samsung S3C24XX MMC/SD/SDIO controller using a device tree. Signed-off-by: Sergio Prado [Arnd: fix broken conditional expression] Signed-off-by: Arnd Bergmann Signed-off-by: Ulf Hansson --- drivers/mmc/host/s3cmci.c | 257 +++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 131 deletions(-) diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c index 5c9037ebc44e..8896bf533dc7 100644 --- a/drivers/mmc/host/s3cmci.c +++ b/drivers/mmc/host/s3cmci.c @@ -24,6 +24,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -807,21 +811,6 @@ irq_out: } -/* - * ISR for the CardDetect Pin -*/ - -static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id) -{ - struct s3cmci_host *host = (struct s3cmci_host *)dev_id; - - dbg(host, dbg_irq, "card detect\n"); - - mmc_detect_change(host->mmc, msecs_to_jiffies(500)); - - return IRQ_HANDLED; -} - static void s3cmci_dma_done_callback(void *arg) { struct s3cmci_host *host = arg; @@ -1177,19 +1166,6 @@ static void s3cmci_send_request(struct mmc_host *mmc) s3cmci_enable_irq(host, true); } -static int s3cmci_card_present(struct mmc_host *mmc) -{ - struct s3cmci_host *host = mmc_priv(mmc); - struct s3c24xx_mci_pdata *pdata = host->pdata; - int ret; - - if (pdata->no_detect) - return -ENOSYS; - - ret = gpio_get_value(pdata->gpio_detect) ? 0 : 1; - return ret ^ pdata->detect_invert; -} - static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct s3cmci_host *host = mmc_priv(mmc); @@ -1198,7 +1174,7 @@ static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) host->cmd_is_stop = 0; host->mrq = mrq; - if (s3cmci_card_present(mmc) == 0) { + if (mmc_gpio_get_cd(mmc) == 0) { dbg(host, dbg_err, "%s: no medium present\n", __func__); host->mrq->cmd->error = -ENOMEDIUM; mmc_request_done(mmc, mrq); @@ -1242,8 +1218,9 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) case MMC_POWER_ON: case MMC_POWER_UP: /* Configure GPE5...GPE10 pins in SD mode */ - s3c_gpio_cfgall_range(S3C2410_GPE(5), 6, S3C_GPIO_SFN(2), - S3C_GPIO_PULL_NONE); + if (!host->pdev->dev.of_node) + s3c_gpio_cfgall_range(S3C2410_GPE(5), 6, S3C_GPIO_SFN(2), + S3C_GPIO_PULL_NONE); if (host->pdata->set_power) host->pdata->set_power(ios->power_mode, ios->vdd); @@ -1255,7 +1232,8 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) case MMC_POWER_OFF: default: - gpio_direction_output(S3C2410_GPE(5), 0); + if (!host->pdev->dev.of_node) + gpio_direction_output(S3C2410_GPE(5), 0); if (host->is2440) mci_con |= S3C2440_SDICON_SDRESET; @@ -1295,21 +1273,6 @@ static void s3cmci_reset(struct s3cmci_host *host) writel(con, host->base + S3C2410_SDICON); } -static int s3cmci_get_ro(struct mmc_host *mmc) -{ - struct s3cmci_host *host = mmc_priv(mmc); - struct s3c24xx_mci_pdata *pdata = host->pdata; - int ret; - - if (pdata->no_wprotect) - return 0; - - ret = gpio_get_value(pdata->gpio_wprotect) ? 1 : 0; - ret ^= pdata->wprotect_invert; - - return ret; -} - static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable) { struct s3cmci_host *host = mmc_priv(mmc); @@ -1353,8 +1316,8 @@ static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable) static struct mmc_host_ops s3cmci_ops = { .request = s3cmci_request, .set_ios = s3cmci_set_ios, - .get_ro = s3cmci_get_ro, - .get_cd = s3cmci_card_present, + .get_ro = mmc_gpio_get_ro, + .get_cd = mmc_gpio_get_cd, .enable_sdio_irq = s3cmci_enable_sdio_irq, }; @@ -1545,21 +1508,14 @@ static inline void s3cmci_debugfs_remove(struct s3cmci_host *host) { } #endif /* CONFIG_DEBUG_FS */ -static int s3cmci_probe(struct platform_device *pdev) +static int s3cmci_probe_pdata(struct s3cmci_host *host) { - struct s3cmci_host *host; - struct mmc_host *mmc; - int ret; - int is2440; - int i; + struct platform_device *pdev = host->pdev; + struct mmc_host *mmc = host->mmc; + struct s3c24xx_mci_pdata *pdata; + int i, ret; - is2440 = platform_get_device_id(pdev)->driver_data; - - mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); - if (!mmc) { - ret = -ENOMEM; - goto probe_out; - } + host->is2440 = platform_get_device_id(pdev)->driver_data; for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) { ret = gpio_request(i, dev_name(&pdev->dev)); @@ -1569,25 +1525,101 @@ static int s3cmci_probe(struct platform_device *pdev) for (i--; i >= S3C2410_GPE(5); i--) gpio_free(i); - goto probe_free_host; + return ret; } } + if (!pdev->dev.platform_data) + pdev->dev.platform_data = &s3cmci_def_pdata; + + pdata = pdev->dev.platform_data; + + if (pdata->no_wprotect) + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT; + + if (pdata->no_detect) + mmc->caps |= MMC_CAP_NEEDS_POLL; + + if (pdata->wprotect_invert) + mmc->caps2 |= MMC_CAP2_RO_ACTIVE_HIGH; + + if (pdata->detect_invert) + mmc->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH; + + if (gpio_is_valid(pdata->gpio_detect)) { + ret = mmc_gpio_request_cd(mmc, pdata->gpio_detect, 0); + if (ret) { + dev_err(&pdev->dev, "error requesting GPIO for CD %d\n", + ret); + return ret; + } + } + + if (gpio_is_valid(pdata->gpio_wprotect)) { + ret = mmc_gpio_request_ro(mmc, pdata->gpio_wprotect); + if (ret) { + dev_err(&pdev->dev, "error requesting GPIO for WP %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int s3cmci_probe_dt(struct s3cmci_host *host) +{ + struct platform_device *pdev = host->pdev; + struct s3c24xx_mci_pdata *pdata; + struct mmc_host *mmc = host->mmc; + int ret; + + host->is2440 = (int) of_device_get_match_data(&pdev->dev); + + ret = mmc_of_parse(mmc); + if (ret) + return ret; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdev->dev.platform_data = pdata; + + return 0; +} + +static int s3cmci_probe(struct platform_device *pdev) +{ + struct s3cmci_host *host; + struct mmc_host *mmc; + int ret; + int i; + + mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto probe_out; + } + host = mmc_priv(mmc); host->mmc = mmc; host->pdev = pdev; - host->is2440 = is2440; + + if (pdev->dev.of_node) + ret = s3cmci_probe_dt(host); + else + ret = s3cmci_probe_pdata(host); + + if (ret) + goto probe_free_host; host->pdata = pdev->dev.platform_data; - if (!host->pdata) { - pdev->dev.platform_data = &s3cmci_def_pdata; - host->pdata = &s3cmci_def_pdata; - } spin_lock_init(&host->complete_lock); tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host); - if (is2440) { + if (host->is2440) { host->sdiimsk = S3C2440_SDIIMSK; host->sdidata = S3C2440_SDIDATA; host->clk_div = 1; @@ -1645,43 +1677,6 @@ static int s3cmci_probe(struct platform_device *pdev) disable_irq(host->irq); host->irq_state = false; - if (!host->pdata->no_detect) { - ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect"); - if (ret) { - dev_err(&pdev->dev, "failed to get detect gpio\n"); - goto probe_free_irq; - } - - host->irq_cd = gpio_to_irq(host->pdata->gpio_detect); - - if (host->irq_cd >= 0) { - if (request_irq(host->irq_cd, s3cmci_irq_cd, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING, - DRIVER_NAME, host)) { - dev_err(&pdev->dev, - "can't get card detect irq.\n"); - ret = -ENOENT; - goto probe_free_gpio_cd; - } - } else { - dev_warn(&pdev->dev, - "host detect has no irq available\n"); - gpio_direction_input(host->pdata->gpio_detect); - } - } else - host->irq_cd = -1; - - if (!host->pdata->no_wprotect) { - ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp"); - if (ret) { - dev_err(&pdev->dev, "failed to get writeprotect\n"); - goto probe_free_irq_cd; - } - - gpio_direction_input(host->pdata->gpio_wprotect); - } - /* Depending on the dma state, get a DMA channel to use. */ if (s3cmci_host_usedma(host)) { @@ -1689,7 +1684,7 @@ static int s3cmci_probe(struct platform_device *pdev) ret = PTR_ERR_OR_ZERO(host->dma); if (ret) { dev_err(&pdev->dev, "cannot get DMA channel.\n"); - goto probe_free_gpio_wp; + goto probe_free_irq; } } @@ -1768,18 +1763,6 @@ static int s3cmci_probe(struct platform_device *pdev) if (s3cmci_host_usedma(host)) dma_release_channel(host->dma); - probe_free_gpio_wp: - if (!host->pdata->no_wprotect) - gpio_free(host->pdata->gpio_wprotect); - - probe_free_gpio_cd: - if (!host->pdata->no_detect) - gpio_free(host->pdata->gpio_detect); - - probe_free_irq_cd: - if (host->irq_cd >= 0) - free_irq(host->irq_cd, host); - probe_free_irq: free_irq(host->irq, host); @@ -1790,8 +1773,9 @@ static int s3cmci_probe(struct platform_device *pdev) release_mem_region(host->mem->start, resource_size(host->mem)); probe_free_gpio: - for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) - gpio_free(i); + if (!pdev->dev.of_node) + for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) + gpio_free(i); probe_free_host: mmc_free_host(mmc); @@ -1818,7 +1802,6 @@ static int s3cmci_remove(struct platform_device *pdev) { struct mmc_host *mmc = platform_get_drvdata(pdev); struct s3cmci_host *host = mmc_priv(mmc); - struct s3c24xx_mci_pdata *pd = host->pdata; int i; s3cmci_shutdown(pdev); @@ -1832,15 +1815,9 @@ static int s3cmci_remove(struct platform_device *pdev) free_irq(host->irq, host); - if (!pd->no_wprotect) - gpio_free(pd->gpio_wprotect); - - if (!pd->no_detect) - gpio_free(pd->gpio_detect); - - for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) - gpio_free(i); - + if (!pdev->dev.of_node) + for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) + gpio_free(i); iounmap(host->base); release_mem_region(host->mem->start, resource_size(host->mem)); @@ -1849,6 +1826,23 @@ static int s3cmci_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id s3cmci_dt_match[] = { + { + .compatible = "samsung,s3c2410-sdi", + .data = (void *)0, + }, + { + .compatible = "samsung,s3c2412-sdi", + .data = (void *)1, + }, + { + .compatible = "samsung,s3c2440-sdi", + .data = (void *)1, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s3cmci_dt_match); + static const struct platform_device_id s3cmci_driver_ids[] = { { .name = "s3c2410-sdi", @@ -1868,6 +1862,7 @@ MODULE_DEVICE_TABLE(platform, s3cmci_driver_ids); static struct platform_driver s3cmci_driver = { .driver = { .name = "s3c-sdi", + .of_match_table = s3cmci_dt_match, }, .id_table = s3cmci_driver_ids, .probe = s3cmci_probe, From 8ddfe07e18c9c82f7567d3cfbd68d8b59764d015 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:34 +0200 Subject: [PATCH 092/148] mmc: block: Use local var for mqrq_cur A subsequent patch will remove 'mq->mqrq_cur'. Prepare for that by assigning it to a local variable. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index ff3da960c473..d317e5e9b79a 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -1597,7 +1597,8 @@ static void mmc_blk_rw_cmd_abort(struct mmc_card *card, struct request *req) * @mq: the queue with the card and host to restart * @req: a new request that want to be started after the current one */ -static void mmc_blk_rw_try_restart(struct mmc_queue *mq, struct request *req) +static void mmc_blk_rw_try_restart(struct mmc_queue *mq, struct request *req, + struct mmc_queue_req *mqrq) { if (!req) return; @@ -1611,8 +1612,8 @@ static void mmc_blk_rw_try_restart(struct mmc_queue *mq, struct request *req) return; } /* Else proceed and try to restart the current async request */ - mmc_blk_rw_rq_prep(mq->mqrq_cur, mq->card, 0, mq); - mmc_start_areq(mq->card->host, &mq->mqrq_cur->areq, NULL); + mmc_blk_rw_rq_prep(mqrq, mq->card, 0, mq); + mmc_start_areq(mq->card->host, &mqrq->areq, NULL); } static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) @@ -1622,6 +1623,7 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) struct mmc_blk_request *brq; int disable_multi = 0, retry = 0, type, retune_retry_done = 0; enum mmc_blk_status status; + struct mmc_queue_req *mqrq_cur = mq->mqrq_cur; struct mmc_queue_req *mq_rq; struct request *old_req; struct mmc_async_req *new_areq; @@ -1645,8 +1647,8 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) return; } - mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq); - new_areq = &mq->mqrq_cur->areq; + mmc_blk_rw_rq_prep(mqrq_cur, card, 0, mq); + new_areq = &mqrq_cur->areq; } else new_areq = NULL; @@ -1700,11 +1702,11 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) if (mmc_blk_reset(md, card->host, type)) { if (req_pending) mmc_blk_rw_cmd_abort(card, old_req); - mmc_blk_rw_try_restart(mq, new_req); + mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } if (!req_pending) { - mmc_blk_rw_try_restart(mq, new_req); + mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } break; @@ -1717,7 +1719,7 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) if (!mmc_blk_reset(md, card->host, type)) break; mmc_blk_rw_cmd_abort(card, old_req); - mmc_blk_rw_try_restart(mq, new_req); + mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; case MMC_BLK_DATA_ERR: { int err; @@ -1727,7 +1729,7 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) break; if (err == -ENODEV) { mmc_blk_rw_cmd_abort(card, old_req); - mmc_blk_rw_try_restart(mq, new_req); + mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } /* Fall through */ @@ -1748,19 +1750,19 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) req_pending = blk_end_request(old_req, -EIO, brq->data.blksz); if (!req_pending) { - mmc_blk_rw_try_restart(mq, new_req); + mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } break; case MMC_BLK_NOMEDIUM: mmc_blk_rw_cmd_abort(card, old_req); - mmc_blk_rw_try_restart(mq, new_req); + mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; default: pr_err("%s: Unhandled return value (%d)", old_req->rq_disk->disk_name, status); mmc_blk_rw_cmd_abort(card, old_req); - mmc_blk_rw_try_restart(mq, new_req); + mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } From cdf8a6fb48882651049e468e6b16956fb83db86c Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:35 +0200 Subject: [PATCH 093/148] mmc: block: Introduce queue semantics Change from viewing the requests in progress as 'current' and 'previous', to viewing them as a queue. The current request is allocated to the first free slot. The presence of incomplete requests is determined from the count (mq->qcnt) of entries in the queue. Non-read-write requests (i.e. discards and flushes) are not added to the queue at all and require no special handling. Also no special handling is needed for the MMC_BLK_NEW_REQUEST case. As well as allowing an arbitrarily sized queue, the queue thread function is significantly simpler. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 66 +++++++++++++++++++++-------------- drivers/mmc/core/queue.c | 75 +++++++++++++++++++++++++--------------- drivers/mmc/core/queue.h | 10 ++++-- 3 files changed, 95 insertions(+), 56 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index d317e5e9b79a..16c313a62129 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -129,6 +129,13 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, struct mmc_blk_data *md); static int get_card_status(struct mmc_card *card, u32 *status, int retries); +static void mmc_blk_requeue(struct request_queue *q, struct request *req) +{ + spin_lock_irq(q->queue_lock); + blk_requeue_request(q, req); + spin_unlock_irq(q->queue_lock); +} + static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk) { struct mmc_blk_data *md; @@ -1585,11 +1592,14 @@ static bool mmc_blk_rw_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, return req_pending; } -static void mmc_blk_rw_cmd_abort(struct mmc_card *card, struct request *req) +static void mmc_blk_rw_cmd_abort(struct mmc_queue *mq, struct mmc_card *card, + struct request *req, + struct mmc_queue_req *mqrq) { if (mmc_card_removed(card)) req->rq_flags |= RQF_QUIET; while (blk_end_request(req, -EIO, blk_rq_cur_bytes(req))); + mmc_queue_req_free(mq, mqrq); } /** @@ -1609,6 +1619,7 @@ static void mmc_blk_rw_try_restart(struct mmc_queue *mq, struct request *req, if (mmc_card_removed(mq->card)) { req->rq_flags |= RQF_QUIET; blk_end_request_all(req, -EIO); + mmc_queue_req_free(mq, mqrq); return; } /* Else proceed and try to restart the current async request */ @@ -1623,14 +1634,23 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) struct mmc_blk_request *brq; int disable_multi = 0, retry = 0, type, retune_retry_done = 0; enum mmc_blk_status status; - struct mmc_queue_req *mqrq_cur = mq->mqrq_cur; + struct mmc_queue_req *mqrq_cur = NULL; struct mmc_queue_req *mq_rq; struct request *old_req; struct mmc_async_req *new_areq; struct mmc_async_req *old_areq; bool req_pending = true; - if (!new_req && !mq->mqrq_prev->req) + if (new_req) { + mqrq_cur = mmc_queue_req_find(mq, new_req); + if (!mqrq_cur) { + WARN_ON(1); + mmc_blk_requeue(mq->queue, new_req); + new_req = NULL; + } + } + + if (!mq->qcnt) return; do { @@ -1643,7 +1663,7 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) !IS_ALIGNED(blk_rq_sectors(new_req), 8)) { pr_err("%s: Transfer size is not 4KB sector size aligned\n", new_req->rq_disk->disk_name); - mmc_blk_rw_cmd_abort(card, new_req); + mmc_blk_rw_cmd_abort(mq, card, new_req, mqrq_cur); return; } @@ -1659,8 +1679,6 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) * and there is nothing more to do until it is * complete. */ - if (status == MMC_BLK_NEW_REQUEST) - mq->new_request = true; return; } @@ -1693,7 +1711,7 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) pr_err("%s BUG rq_tot %d d_xfer %d\n", __func__, blk_rq_bytes(old_req), brq->data.bytes_xfered); - mmc_blk_rw_cmd_abort(card, old_req); + mmc_blk_rw_cmd_abort(mq, card, old_req, mq_rq); return; } break; @@ -1701,11 +1719,14 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) req_pending = mmc_blk_rw_cmd_err(md, card, brq, old_req, req_pending); if (mmc_blk_reset(md, card->host, type)) { if (req_pending) - mmc_blk_rw_cmd_abort(card, old_req); + mmc_blk_rw_cmd_abort(mq, card, old_req, mq_rq); + else + mmc_queue_req_free(mq, mq_rq); mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } if (!req_pending) { + mmc_queue_req_free(mq, mq_rq); mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } @@ -1718,7 +1739,7 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) case MMC_BLK_ABORT: if (!mmc_blk_reset(md, card->host, type)) break; - mmc_blk_rw_cmd_abort(card, old_req); + mmc_blk_rw_cmd_abort(mq, card, old_req, mq_rq); mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; case MMC_BLK_DATA_ERR: { @@ -1728,7 +1749,7 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) if (!err) break; if (err == -ENODEV) { - mmc_blk_rw_cmd_abort(card, old_req); + mmc_blk_rw_cmd_abort(mq, card, old_req, mq_rq); mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } @@ -1750,18 +1771,19 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) req_pending = blk_end_request(old_req, -EIO, brq->data.blksz); if (!req_pending) { + mmc_queue_req_free(mq, mq_rq); mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } break; case MMC_BLK_NOMEDIUM: - mmc_blk_rw_cmd_abort(card, old_req); + mmc_blk_rw_cmd_abort(mq, card, old_req, mq_rq); mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; default: pr_err("%s: Unhandled return value (%d)", old_req->rq_disk->disk_name, status); - mmc_blk_rw_cmd_abort(card, old_req); + mmc_blk_rw_cmd_abort(mq, card, old_req, mq_rq); mmc_blk_rw_try_restart(mq, new_req, mqrq_cur); return; } @@ -1778,6 +1800,8 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req) mq_rq->brq.retune_retry_done = retune_retry_done; } } while (req_pending); + + mmc_queue_req_free(mq, mq_rq); } void mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) @@ -1785,9 +1809,8 @@ void mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) int ret; struct mmc_blk_data *md = mq->blkdata; struct mmc_card *card = md->queue.card; - bool req_is_special = mmc_req_is_special(req); - if (req && !mq->mqrq_prev->req) + if (req && !mq->qcnt) /* claim host only for the first request */ mmc_get_card(card); @@ -1799,20 +1822,19 @@ void mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) goto out; } - mq->new_request = false; if (req && req_op(req) == REQ_OP_DISCARD) { /* complete ongoing async transfer before issuing discard */ - if (card->host->areq) + if (mq->qcnt) mmc_blk_issue_rw_rq(mq, NULL); mmc_blk_issue_discard_rq(mq, req); } else if (req && req_op(req) == REQ_OP_SECURE_ERASE) { /* complete ongoing async transfer before issuing secure erase*/ - if (card->host->areq) + if (mq->qcnt) mmc_blk_issue_rw_rq(mq, NULL); mmc_blk_issue_secdiscard_rq(mq, req); } else if (req && req_op(req) == REQ_OP_FLUSH) { /* complete ongoing async transfer before issuing flush */ - if (card->host->areq) + if (mq->qcnt) mmc_blk_issue_rw_rq(mq, NULL); mmc_blk_issue_flush(mq, req); } else { @@ -1821,13 +1843,7 @@ void mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) } out: - if ((!req && !mq->new_request) || req_is_special) - /* - * Release host when there are no more requests - * and after special request(discard, flush) is done. - * In case sepecial request, there is no reentry to - * the 'mmc_blk_issue_rq' with 'mqrq_prev->req'. - */ + if (!mq->qcnt) mmc_put_card(card); } diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c index 493eb10ce580..4a2045527b62 100644 --- a/drivers/mmc/core/queue.c +++ b/drivers/mmc/core/queue.c @@ -40,6 +40,35 @@ static int mmc_prep_request(struct request_queue *q, struct request *req) return BLKPREP_OK; } +struct mmc_queue_req *mmc_queue_req_find(struct mmc_queue *mq, + struct request *req) +{ + struct mmc_queue_req *mqrq; + int i = ffz(mq->qslots); + + if (i >= mq->qdepth) + return NULL; + + mqrq = &mq->mqrq[i]; + WARN_ON(mqrq->req || mq->qcnt >= mq->qdepth || + test_bit(mqrq->task_id, &mq->qslots)); + mqrq->req = req; + mq->qcnt += 1; + __set_bit(mqrq->task_id, &mq->qslots); + + return mqrq; +} + +void mmc_queue_req_free(struct mmc_queue *mq, + struct mmc_queue_req *mqrq) +{ + WARN_ON(!mqrq->req || mq->qcnt < 1 || + !test_bit(mqrq->task_id, &mq->qslots)); + mqrq->req = NULL; + mq->qcnt -= 1; + __clear_bit(mqrq->task_id, &mq->qslots); +} + static int mmc_queue_thread(void *d) { struct mmc_queue *mq = d; @@ -50,7 +79,7 @@ static int mmc_queue_thread(void *d) down(&mq->thread_sem); do { - struct request *req = NULL; + struct request *req; spin_lock_irq(q->queue_lock); set_current_state(TASK_INTERRUPTIBLE); @@ -63,38 +92,17 @@ static int mmc_queue_thread(void *d) * Dispatch queue is empty so set flags for * mmc_request_fn() to wake us up. */ - if (mq->mqrq_prev->req) + if (mq->qcnt) cntx->is_waiting_last_req = true; else mq->asleep = true; } - mq->mqrq_cur->req = req; spin_unlock_irq(q->queue_lock); - if (req || mq->mqrq_prev->req) { - bool req_is_special = mmc_req_is_special(req); - + if (req || mq->qcnt) { set_current_state(TASK_RUNNING); mmc_blk_issue_rq(mq, req); cond_resched(); - if (mq->new_request) { - mq->new_request = false; - continue; /* fetch again */ - } - - /* - * Current request becomes previous request - * and vice versa. - * In case of special requests, current request - * has been finished. Do not assign it to previous - * request. - */ - if (req_is_special) - mq->mqrq_cur->req = NULL; - - mq->mqrq_prev->brq.mrq.data = NULL; - mq->mqrq_prev->req = NULL; - swap(mq->mqrq_prev, mq->mqrq_cur); } else { if (kthread_should_stop()) { set_current_state(TASK_RUNNING); @@ -177,6 +185,20 @@ static void mmc_queue_setup_discard(struct request_queue *q, queue_flag_set_unlocked(QUEUE_FLAG_SECERASE, q); } +static struct mmc_queue_req *mmc_queue_alloc_mqrqs(int qdepth) +{ + struct mmc_queue_req *mqrq; + int i; + + mqrq = kcalloc(qdepth, sizeof(*mqrq), GFP_KERNEL); + if (mqrq) { + for (i = 0; i < qdepth; i++) + mqrq[i].task_id = i; + } + + return mqrq; +} + #ifdef CONFIG_MMC_BLOCK_BOUNCE static bool mmc_queue_alloc_bounce_bufs(struct mmc_queue *mq, unsigned int bouncesz) @@ -279,12 +301,9 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, return -ENOMEM; mq->qdepth = 2; - mq->mqrq = kcalloc(mq->qdepth, sizeof(struct mmc_queue_req), - GFP_KERNEL); + mq->mqrq = mmc_queue_alloc_mqrqs(mq->qdepth); if (!mq->mqrq) goto blk_cleanup; - mq->mqrq_cur = &mq->mqrq[0]; - mq->mqrq_prev = &mq->mqrq[1]; mq->queue->queuedata = mq; blk_queue_prep_rq(mq->queue, mmc_prep_request); diff --git a/drivers/mmc/core/queue.h b/drivers/mmc/core/queue.h index e298f100101b..967808df45b8 100644 --- a/drivers/mmc/core/queue.h +++ b/drivers/mmc/core/queue.h @@ -34,21 +34,21 @@ struct mmc_queue_req { struct scatterlist *bounce_sg; unsigned int bounce_sg_len; struct mmc_async_req areq; + int task_id; }; struct mmc_queue { struct mmc_card *card; struct task_struct *thread; struct semaphore thread_sem; - bool new_request; bool suspended; bool asleep; struct mmc_blk_data *blkdata; struct request_queue *queue; struct mmc_queue_req *mqrq; - struct mmc_queue_req *mqrq_cur; - struct mmc_queue_req *mqrq_prev; int qdepth; + int qcnt; + unsigned long qslots; }; extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, @@ -64,4 +64,8 @@ extern void mmc_queue_bounce_post(struct mmc_queue_req *); extern int mmc_access_rpmb(struct mmc_queue *); +extern struct mmc_queue_req *mmc_queue_req_find(struct mmc_queue *, + struct request *); +extern void mmc_queue_req_free(struct mmc_queue *, struct mmc_queue_req *); + #endif From 7b410d074b253a44624497a18e73f666a9574f37 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:36 +0200 Subject: [PATCH 094/148] mmc: queue: Share mmc request array between partitions eMMC can have multiple internal partitions that are represented as separate disks / queues. However switching between partitions is only done when the queue is empty. Consequently the array of mmc requests that are queued can be shared between partitions saving memory. Keep a pointer to the mmc request queue on the card, and use that instead of allocating a new one for each partition. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 11 +- drivers/mmc/core/queue.c | 240 +++++++++++++++++++++++---------------- drivers/mmc/core/queue.h | 2 + include/linux/mmc/card.h | 5 + 4 files changed, 159 insertions(+), 99 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 16c313a62129..018488e7f194 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -2123,6 +2123,7 @@ static int mmc_blk_probe(struct mmc_card *card) { struct mmc_blk_data *md, *part_md; char cap_str[10]; + int ret; /* * Check that the card supports the command class(es) we need. @@ -2132,9 +2133,15 @@ static int mmc_blk_probe(struct mmc_card *card) mmc_fixup_device(card, mmc_blk_fixups); + ret = mmc_queue_alloc_shared_queue(card); + if (ret) + return ret; + md = mmc_blk_alloc(card); - if (IS_ERR(md)) + if (IS_ERR(md)) { + mmc_queue_free_shared_queue(card); return PTR_ERR(md); + } string_get_size((u64)get_capacity(md->disk), 512, STRING_UNITS_2, cap_str, sizeof(cap_str)); @@ -2172,6 +2179,7 @@ static int mmc_blk_probe(struct mmc_card *card) out: mmc_blk_remove_parts(card, md); mmc_blk_remove_req(md); + mmc_queue_free_shared_queue(card); return 0; } @@ -2189,6 +2197,7 @@ static void mmc_blk_remove(struct mmc_card *card) pm_runtime_put_noidle(&card->dev); mmc_blk_remove_req(md); dev_set_drvdata(&card->dev, NULL); + mmc_queue_free_shared_queue(card); } static int _mmc_blk_suspend(struct mmc_card *card) diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c index 4a2045527b62..3423b7acf744 100644 --- a/drivers/mmc/core/queue.c +++ b/drivers/mmc/core/queue.c @@ -149,17 +149,13 @@ static void mmc_request_fn(struct request_queue *q) wake_up_process(mq->thread); } -static struct scatterlist *mmc_alloc_sg(int sg_len, int *err) +static struct scatterlist *mmc_alloc_sg(int sg_len) { struct scatterlist *sg; sg = kmalloc_array(sg_len, sizeof(*sg), GFP_KERNEL); - if (!sg) - *err = -ENOMEM; - else { - *err = 0; + if (sg) sg_init_table(sg, sg_len); - } return sg; } @@ -185,6 +181,32 @@ static void mmc_queue_setup_discard(struct request_queue *q, queue_flag_set_unlocked(QUEUE_FLAG_SECERASE, q); } +static void mmc_queue_req_free_bufs(struct mmc_queue_req *mqrq) +{ + kfree(mqrq->bounce_sg); + mqrq->bounce_sg = NULL; + + kfree(mqrq->sg); + mqrq->sg = NULL; + + kfree(mqrq->bounce_buf); + mqrq->bounce_buf = NULL; +} + +static void mmc_queue_reqs_free_bufs(struct mmc_queue_req *mqrq, int qdepth) +{ + int i; + + for (i = 0; i < qdepth; i++) + mmc_queue_req_free_bufs(&mqrq[i]); +} + +static void mmc_queue_free_mqrqs(struct mmc_queue_req *mqrq, int qdepth) +{ + mmc_queue_reqs_free_bufs(mqrq, qdepth); + kfree(mqrq); +} + static struct mmc_queue_req *mmc_queue_alloc_mqrqs(int qdepth) { struct mmc_queue_req *mqrq; @@ -200,79 +222,137 @@ static struct mmc_queue_req *mmc_queue_alloc_mqrqs(int qdepth) } #ifdef CONFIG_MMC_BLOCK_BOUNCE -static bool mmc_queue_alloc_bounce_bufs(struct mmc_queue *mq, - unsigned int bouncesz) +static int mmc_queue_alloc_bounce_bufs(struct mmc_queue_req *mqrq, int qdepth, + unsigned int bouncesz) { int i; - for (i = 0; i < mq->qdepth; i++) { - mq->mqrq[i].bounce_buf = kmalloc(bouncesz, GFP_KERNEL); - if (!mq->mqrq[i].bounce_buf) - goto out_err; + for (i = 0; i < qdepth; i++) { + mqrq[i].bounce_buf = kmalloc(bouncesz, GFP_KERNEL); + if (!mqrq[i].bounce_buf) + return -ENOMEM; + + mqrq[i].sg = mmc_alloc_sg(1); + if (!mqrq[i].sg) + return -ENOMEM; + + mqrq[i].bounce_sg = mmc_alloc_sg(bouncesz / 512); + if (!mqrq[i].bounce_sg) + return -ENOMEM; } - return true; + return 0; +} -out_err: - while (--i >= 0) { - kfree(mq->mqrq[i].bounce_buf); - mq->mqrq[i].bounce_buf = NULL; - } - pr_warn("%s: unable to allocate bounce buffers\n", - mmc_card_name(mq->card)); +static bool mmc_queue_alloc_bounce(struct mmc_queue_req *mqrq, int qdepth, + unsigned int bouncesz) +{ + int ret; + + ret = mmc_queue_alloc_bounce_bufs(mqrq, qdepth, bouncesz); + if (ret) + mmc_queue_reqs_free_bufs(mqrq, qdepth); + + return !ret; +} + +static unsigned int mmc_queue_calc_bouncesz(struct mmc_host *host) +{ + unsigned int bouncesz = MMC_QUEUE_BOUNCESZ; + + if (host->max_segs != 1) + return 0; + + if (bouncesz > host->max_req_size) + bouncesz = host->max_req_size; + if (bouncesz > host->max_seg_size) + bouncesz = host->max_seg_size; + if (bouncesz > host->max_blk_count * 512) + bouncesz = host->max_blk_count * 512; + + if (bouncesz <= 512) + return 0; + + return bouncesz; +} +#else +static inline bool mmc_queue_alloc_bounce(struct mmc_queue_req *mqrq, + int qdepth, unsigned int bouncesz) +{ return false; } -static int mmc_queue_alloc_bounce_sgs(struct mmc_queue *mq, - unsigned int bouncesz) +static unsigned int mmc_queue_calc_bouncesz(struct mmc_host *host) { - int i, ret; - - for (i = 0; i < mq->qdepth; i++) { - mq->mqrq[i].sg = mmc_alloc_sg(1, &ret); - if (ret) - return ret; - - mq->mqrq[i].bounce_sg = mmc_alloc_sg(bouncesz / 512, &ret); - if (ret) - return ret; - } - return 0; } #endif -static int mmc_queue_alloc_sgs(struct mmc_queue *mq, int max_segs) +static int mmc_queue_alloc_sgs(struct mmc_queue_req *mqrq, int qdepth, + int max_segs) { - int i, ret; + int i; - for (i = 0; i < mq->qdepth; i++) { - mq->mqrq[i].sg = mmc_alloc_sg(max_segs, &ret); - if (ret) - return ret; + for (i = 0; i < qdepth; i++) { + mqrq[i].sg = mmc_alloc_sg(max_segs); + if (!mqrq[i].sg) + return -ENOMEM; } return 0; } -static void mmc_queue_req_free_bufs(struct mmc_queue_req *mqrq) +void mmc_queue_free_shared_queue(struct mmc_card *card) { - kfree(mqrq->bounce_sg); - mqrq->bounce_sg = NULL; - - kfree(mqrq->sg); - mqrq->sg = NULL; - - kfree(mqrq->bounce_buf); - mqrq->bounce_buf = NULL; + if (card->mqrq) { + mmc_queue_free_mqrqs(card->mqrq, card->qdepth); + card->mqrq = NULL; + } } -static void mmc_queue_reqs_free_bufs(struct mmc_queue *mq) +static int __mmc_queue_alloc_shared_queue(struct mmc_card *card, int qdepth) { - int i; + struct mmc_host *host = card->host; + struct mmc_queue_req *mqrq; + unsigned int bouncesz; + int ret = 0; - for (i = 0; i < mq->qdepth; i++) - mmc_queue_req_free_bufs(&mq->mqrq[i]); + if (card->mqrq) + return -EINVAL; + + mqrq = mmc_queue_alloc_mqrqs(qdepth); + if (!mqrq) + return -ENOMEM; + + card->mqrq = mqrq; + card->qdepth = qdepth; + + bouncesz = mmc_queue_calc_bouncesz(host); + + if (bouncesz && !mmc_queue_alloc_bounce(mqrq, qdepth, bouncesz)) { + bouncesz = 0; + pr_warn("%s: unable to allocate bounce buffers\n", + mmc_card_name(card)); + } + + card->bouncesz = bouncesz; + + if (!bouncesz) { + ret = mmc_queue_alloc_sgs(mqrq, qdepth, host->max_segs); + if (ret) + goto out_err; + } + + return ret; + +out_err: + mmc_queue_free_shared_queue(card); + return ret; +} + +int mmc_queue_alloc_shared_queue(struct mmc_card *card) +{ + return __mmc_queue_alloc_shared_queue(card, 2); } /** @@ -289,7 +369,6 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, { struct mmc_host *host = card->host; u64 limit = BLK_BOUNCE_HIGH; - bool bounce = false; int ret = -ENOMEM; if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask) @@ -300,10 +379,8 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, if (!mq->queue) return -ENOMEM; - mq->qdepth = 2; - mq->mqrq = mmc_queue_alloc_mqrqs(mq->qdepth); - if (!mq->mqrq) - goto blk_cleanup; + mq->mqrq = card->mqrq; + mq->qdepth = card->qdepth; mq->queue->queuedata = mq; blk_queue_prep_rq(mq->queue, mmc_prep_request); @@ -312,44 +389,17 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, if (mmc_can_erase(card)) mmc_queue_setup_discard(mq->queue, card); -#ifdef CONFIG_MMC_BLOCK_BOUNCE - if (host->max_segs == 1) { - unsigned int bouncesz; - - bouncesz = MMC_QUEUE_BOUNCESZ; - - if (bouncesz > host->max_req_size) - bouncesz = host->max_req_size; - if (bouncesz > host->max_seg_size) - bouncesz = host->max_seg_size; - if (bouncesz > (host->max_blk_count * 512)) - bouncesz = host->max_blk_count * 512; - - if (bouncesz > 512 && - mmc_queue_alloc_bounce_bufs(mq, bouncesz)) { - blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_ANY); - blk_queue_max_hw_sectors(mq->queue, bouncesz / 512); - blk_queue_max_segments(mq->queue, bouncesz / 512); - blk_queue_max_segment_size(mq->queue, bouncesz); - - ret = mmc_queue_alloc_bounce_sgs(mq, bouncesz); - if (ret) - goto cleanup_queue; - bounce = true; - } - } -#endif - - if (!bounce) { + if (card->bouncesz) { + blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_ANY); + blk_queue_max_hw_sectors(mq->queue, card->bouncesz / 512); + blk_queue_max_segments(mq->queue, card->bouncesz / 512); + blk_queue_max_segment_size(mq->queue, card->bouncesz); + } else { blk_queue_bounce_limit(mq->queue, limit); blk_queue_max_hw_sectors(mq->queue, min(host->max_blk_count, host->max_req_size / 512)); blk_queue_max_segments(mq->queue, host->max_segs); blk_queue_max_segment_size(mq->queue, host->max_seg_size); - - ret = mmc_queue_alloc_sgs(mq, host->max_segs); - if (ret) - goto cleanup_queue; } sema_init(&mq->thread_sem, 1); @@ -364,11 +414,8 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, return 0; - cleanup_queue: - mmc_queue_reqs_free_bufs(mq); - kfree(mq->mqrq); +cleanup_queue: mq->mqrq = NULL; -blk_cleanup: blk_cleanup_queue(mq->queue); return ret; } @@ -390,10 +437,7 @@ void mmc_cleanup_queue(struct mmc_queue *mq) blk_start_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); - mmc_queue_reqs_free_bufs(mq); - kfree(mq->mqrq); mq->mqrq = NULL; - mq->card = NULL; } EXPORT_SYMBOL(mmc_cleanup_queue); diff --git a/drivers/mmc/core/queue.h b/drivers/mmc/core/queue.h index 967808df45b8..871796c3f406 100644 --- a/drivers/mmc/core/queue.h +++ b/drivers/mmc/core/queue.h @@ -51,6 +51,8 @@ struct mmc_queue { unsigned long qslots; }; +extern int mmc_queue_alloc_shared_queue(struct mmc_card *card); +extern void mmc_queue_free_shared_queue(struct mmc_card *card); extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, const char *); extern void mmc_cleanup_queue(struct mmc_queue *); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 77e61e0a216a..119ef8f0155c 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -208,6 +208,7 @@ struct sdio_cis { struct mmc_host; struct sdio_func; struct sdio_func_tuple; +struct mmc_queue_req; #define SDIO_MAX_FUNCS 7 @@ -300,6 +301,10 @@ struct mmc_card { struct dentry *debugfs_root; struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ unsigned int nr_parts; + + struct mmc_queue_req *mqrq; /* Shared queue structure */ + unsigned int bouncesz; /* Bounce buffer size */ + int qdepth; /* Shared queue depth */ }; static inline bool mmc_large_sector(struct mmc_card *card) From b658af718465cd1e8011c8da281befdfc2debefd Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:37 +0200 Subject: [PATCH 095/148] mmc: mmc: Add functions to enable / disable the Command Queue Add helper functions to enable or disable the Command Queue. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- Documentation/mmc/mmc-dev-attrs.txt | 1 + drivers/mmc/core/mmc.c | 2 ++ drivers/mmc/core/mmc_ops.c | 28 ++++++++++++++++++++++++++++ drivers/mmc/core/mmc_ops.h | 2 ++ include/linux/mmc/card.h | 1 + 5 files changed, 34 insertions(+) diff --git a/Documentation/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt index 404a0e9e92b0..dcd1252877fb 100644 --- a/Documentation/mmc/mmc-dev-attrs.txt +++ b/Documentation/mmc/mmc-dev-attrs.txt @@ -30,6 +30,7 @@ All attributes are read-only. rel_sectors Reliable write sector count ocr Operation Conditions Register dsr Driver Stage Register + cmdq_en Command Queue enabled: 1 => enabled, 0 => not enabled Note on Erase Size and Preferred Erase Size: diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index b502601df228..35b2d0f767ca 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -790,6 +790,7 @@ MMC_DEV_ATTR(enhanced_area_size, "%u\n", card->ext_csd.enhanced_area_size); MMC_DEV_ATTR(raw_rpmb_size_mult, "%#x\n", card->ext_csd.raw_rpmb_size_mult); MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors); MMC_DEV_ATTR(ocr, "%08x\n", card->ocr); +MMC_DEV_ATTR(cmdq_en, "%d\n", card->ext_csd.cmdq_en); static ssize_t mmc_fwrev_show(struct device *dev, struct device_attribute *attr, @@ -845,6 +846,7 @@ static struct attribute *mmc_std_attrs[] = { &dev_attr_rel_sectors.attr, &dev_attr_ocr.attr, &dev_attr_dsr.attr, + &dev_attr_cmdq_en.attr, NULL, }; ATTRIBUTE_GROUPS(mmc_std); diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index fe80f26d6971..24c58d24c19a 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -838,3 +838,31 @@ int mmc_can_ext_csd(struct mmc_card *card) { return (card && card->csd.mmca_vsn > CSD_SPEC_VER_3); } + +static int mmc_cmdq_switch(struct mmc_card *card, bool enable) +{ + u8 val = enable ? EXT_CSD_CMDQ_MODE_ENABLED : 0; + int err; + + if (!card->ext_csd.cmdq_support) + return -EOPNOTSUPP; + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CMDQ_MODE_EN, + val, card->ext_csd.generic_cmd6_time); + if (!err) + card->ext_csd.cmdq_en = enable; + + return err; +} + +int mmc_cmdq_enable(struct mmc_card *card) +{ + return mmc_cmdq_switch(card, true); +} +EXPORT_SYMBOL_GPL(mmc_cmdq_enable); + +int mmc_cmdq_disable(struct mmc_card *card) +{ + return mmc_cmdq_switch(card, false); +} +EXPORT_SYMBOL_GPL(mmc_cmdq_disable); diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index 74beea8a9c7e..978bd2e60f8a 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -46,6 +46,8 @@ int mmc_read_bkops_status(struct mmc_card *card); void mmc_start_bkops(struct mmc_card *card, bool from_exception); int mmc_can_reset(struct mmc_card *card); int mmc_flush_cache(struct mmc_card *card); +int mmc_cmdq_enable(struct mmc_card *card); +int mmc_cmdq_disable(struct mmc_card *card); #endif diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 119ef8f0155c..94637796b99c 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -89,6 +89,7 @@ struct mmc_ext_csd { unsigned int boot_ro_lock; /* ro lock support */ bool boot_ro_lockable; bool ffu_capable; /* Firmware upgrade support */ + bool cmdq_en; /* Command Queue enabled */ bool cmdq_support; /* Command Queue supported */ unsigned int cmdq_depth; /* Command Queue depth */ #define MMC_FIRMWARE_LEN 8 From 9d4579a85c84340044b10ffa6cd576397f59dc93 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:38 +0200 Subject: [PATCH 096/148] mmc: mmc_test: Disable Command Queue while mmc_test is used Normal read and write commands may not be used while the command queue is enabled. Disable the Command Queue when mmc_test is probed and re-enable it when it is removed. Signed-off-by: Adrian Hunter Reviewed-by: Harjani Ritesh Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/mmc.c | 7 +++++++ drivers/mmc/core/mmc_test.c | 14 ++++++++++++++ include/linux/mmc/card.h | 2 ++ 3 files changed, 23 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 35b2d0f767ca..2c87dede5841 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1789,6 +1789,13 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } + /* + * In some cases (e.g. RPMB or mmc_test), the Command Queue must be + * disabled for a time, so a flag is needed to indicate to re-enable the + * Command Queue. + */ + card->reenable_cmdq = card->ext_csd.cmdq_en; + /* * The mandatory minimum values are defined for packed command. * read: 5, write: 3 diff --git a/drivers/mmc/core/mmc_test.c b/drivers/mmc/core/mmc_test.c index f99ac3123fd2..fd1b4b8510b9 100644 --- a/drivers/mmc/core/mmc_test.c +++ b/drivers/mmc/core/mmc_test.c @@ -26,6 +26,7 @@ #include "card.h" #include "host.h" #include "bus.h" +#include "mmc_ops.h" #define RESULT_OK 0 #define RESULT_FAIL 1 @@ -3264,6 +3265,14 @@ static int mmc_test_probe(struct mmc_card *card) if (ret) return ret; + if (card->ext_csd.cmdq_en) { + mmc_claim_host(card->host); + ret = mmc_cmdq_disable(card); + mmc_release_host(card->host); + if (ret) + return ret; + } + dev_info(&card->dev, "Card claimed for testing.\n"); return 0; @@ -3271,6 +3280,11 @@ static int mmc_test_probe(struct mmc_card *card) static void mmc_test_remove(struct mmc_card *card) { + if (card->reenable_cmdq) { + mmc_claim_host(card->host); + mmc_cmdq_enable(card); + mmc_release_host(card->host); + } mmc_test_free_result(card); mmc_test_free_dbgfs_file(card); } diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 94637796b99c..85b5f2bc8bb9 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -269,6 +269,8 @@ struct mmc_card { #define MMC_QUIRK_TRIM_BROKEN (1<<12) /* Skip trim */ #define MMC_QUIRK_BROKEN_HPI (1<<13) /* Disable broken HPI support */ + bool reenable_cmdq; /* Re-enable Command Queue */ + unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ unsigned int pref_erase; /* in sectors */ From 025e3d5fe157e8a12ed4ab59d0e147046c87cbd9 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:39 +0200 Subject: [PATCH 097/148] mmc: block: Disable Command Queue while RPMB is used RPMB does not allow Command Queue commands. Disable and re-enable the Command Queue when switching. Note that the driver only switches partitions when the queue is empty. Signed-off-by: Adrian Hunter Reviewed-by: Harjani Ritesh Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 46 +++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 018488e7f194..d189e8c4436e 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -728,10 +728,41 @@ static const struct block_device_operations mmc_bdops = { #endif }; +static int mmc_blk_part_switch_pre(struct mmc_card *card, + unsigned int part_type) +{ + int ret = 0; + + if (part_type == EXT_CSD_PART_CONFIG_ACC_RPMB) { + if (card->ext_csd.cmdq_en) { + ret = mmc_cmdq_disable(card); + if (ret) + return ret; + } + mmc_retune_pause(card->host); + } + + return ret; +} + +static int mmc_blk_part_switch_post(struct mmc_card *card, + unsigned int part_type) +{ + int ret = 0; + + if (part_type == EXT_CSD_PART_CONFIG_ACC_RPMB) { + mmc_retune_unpause(card->host); + if (card->reenable_cmdq && !card->ext_csd.cmdq_en) + ret = mmc_cmdq_enable(card); + } + + return ret; +} + static inline int mmc_blk_part_switch(struct mmc_card *card, struct mmc_blk_data *md) { - int ret; + int ret = 0; struct mmc_blk_data *main_md = dev_get_drvdata(&card->dev); if (main_md->part_curr == md->part_type) @@ -740,8 +771,9 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, if (mmc_card_mmc(card)) { u8 part_config = card->ext_csd.part_config; - if (md->part_type == EXT_CSD_PART_CONFIG_ACC_RPMB) - mmc_retune_pause(card->host); + ret = mmc_blk_part_switch_pre(card, md->part_type); + if (ret) + return ret; part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; part_config |= md->part_type; @@ -750,19 +782,17 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, EXT_CSD_PART_CONFIG, part_config, card->ext_csd.part_time); if (ret) { - if (md->part_type == EXT_CSD_PART_CONFIG_ACC_RPMB) - mmc_retune_unpause(card->host); + mmc_blk_part_switch_post(card, md->part_type); return ret; } card->ext_csd.part_config = part_config; - if (main_md->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB) - mmc_retune_unpause(card->host); + ret = mmc_blk_part_switch_post(card, main_md->part_curr); } main_md->part_curr = md->part_type; - return 0; + return ret; } static int mmc_sd_num_wr_blocks(struct mmc_card *card, u32 *written_blocks) From 9cb38f7a2d0aab5730ac18117ab2f5c9e3d4a826 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:40 +0200 Subject: [PATCH 098/148] mmc: block: Change mmc_apply_rel_rw() to get block address from the request mmc_apply_rel_rw() will be used by Software Command Queuing also. In that case the command argument is not the block address so change mmc_apply_rel_rw() to get block address from the request. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index d189e8c4436e..6d2c302c3673 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -1309,7 +1309,7 @@ static inline void mmc_apply_rel_rw(struct mmc_blk_request *brq, { if (!(card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN)) { /* Legacy mode imposes restrictions on transfers. */ - if (!IS_ALIGNED(brq->cmd.arg, card->ext_csd.rel_sectors)) + if (!IS_ALIGNED(blk_rq_pos(req), card->ext_csd.rel_sectors)) brq->data.blocks = 1; if (brq->data.blocks > card->ext_csd.rel_sectors) From ca5717f7ceef3c0aa76e2f77acb95ec3a93470c8 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:41 +0200 Subject: [PATCH 099/148] mmc: block: Factor out data preparation Factor out data preparation into a separate function mmc_blk_data_prep() which can be re-used for command queuing. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 151 +++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 69 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 6d2c302c3673..8273b078686d 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -1433,36 +1433,39 @@ static enum mmc_blk_status mmc_blk_err_check(struct mmc_card *card, return MMC_BLK_SUCCESS; } -static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, - struct mmc_card *card, - int disable_multi, - struct mmc_queue *mq) +static void mmc_blk_data_prep(struct mmc_queue *mq, struct mmc_queue_req *mqrq, + int disable_multi, bool *do_rel_wr, + bool *do_data_tag) { - u32 readcmd, writecmd; + struct mmc_blk_data *md = mq->blkdata; + struct mmc_card *card = md->queue.card; struct mmc_blk_request *brq = &mqrq->brq; struct request *req = mqrq->req; - struct mmc_blk_data *md = mq->blkdata; - bool do_data_tag; /* * Reliable writes are used to implement Forced Unit Access and * are supported only on MMCs. */ - bool do_rel_wr = (req->cmd_flags & REQ_FUA) && - (rq_data_dir(req) == WRITE) && - (md->flags & MMC_BLK_REL_WR); + *do_rel_wr = (req->cmd_flags & REQ_FUA) && + rq_data_dir(req) == WRITE && + (md->flags & MMC_BLK_REL_WR); memset(brq, 0, sizeof(struct mmc_blk_request)); - brq->mrq.cmd = &brq->cmd; + brq->mrq.data = &brq->data; - brq->cmd.arg = blk_rq_pos(req); - if (!mmc_card_blockaddr(card)) - brq->cmd.arg <<= 9; - brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; - brq->data.blksz = 512; brq->stop.opcode = MMC_STOP_TRANSMISSION; brq->stop.arg = 0; + + if (rq_data_dir(req) == READ) { + brq->data.flags = MMC_DATA_READ; + brq->stop.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; + } else { + brq->data.flags = MMC_DATA_WRITE; + brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + } + + brq->data.blksz = 512; brq->data.blocks = blk_rq_sectors(req); /* @@ -1493,6 +1496,68 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, brq->data.blocks); } + if (*do_rel_wr) + mmc_apply_rel_rw(brq, card, req); + + /* + * Data tag is used only during writing meta data to speed + * up write and any subsequent read of this meta data + */ + *do_data_tag = card->ext_csd.data_tag_unit_size && + (req->cmd_flags & REQ_META) && + (rq_data_dir(req) == WRITE) && + ((brq->data.blocks * brq->data.blksz) >= + card->ext_csd.data_tag_unit_size); + + mmc_set_data_timeout(&brq->data, card); + + brq->data.sg = mqrq->sg; + brq->data.sg_len = mmc_queue_map_sg(mq, mqrq); + + /* + * Adjust the sg list so it is the same size as the + * request. + */ + if (brq->data.blocks != blk_rq_sectors(req)) { + int i, data_size = brq->data.blocks << 9; + struct scatterlist *sg; + + for_each_sg(brq->data.sg, sg, brq->data.sg_len, i) { + data_size -= sg->length; + if (data_size <= 0) { + sg->length += data_size; + i++; + break; + } + } + brq->data.sg_len = i; + } + + mqrq->areq.mrq = &brq->mrq; + + mmc_queue_bounce_pre(mqrq); +} + +static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, + struct mmc_card *card, + int disable_multi, + struct mmc_queue *mq) +{ + u32 readcmd, writecmd; + struct mmc_blk_request *brq = &mqrq->brq; + struct request *req = mqrq->req; + struct mmc_blk_data *md = mq->blkdata; + bool do_rel_wr, do_data_tag; + + mmc_blk_data_prep(mq, mqrq, disable_multi, &do_rel_wr, &do_data_tag); + + brq->mrq.cmd = &brq->cmd; + + brq->cmd.arg = blk_rq_pos(req); + if (!mmc_card_blockaddr(card)) + 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) { /* SPI multiblock writes terminate using a special * token, not a STOP_TRANSMISSION request. @@ -1507,32 +1572,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, readcmd = MMC_READ_SINGLE_BLOCK; writecmd = MMC_WRITE_BLOCK; } - if (rq_data_dir(req) == READ) { - brq->cmd.opcode = readcmd; - brq->data.flags = MMC_DATA_READ; - if (brq->mrq.stop) - brq->stop.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | - MMC_CMD_AC; - } else { - brq->cmd.opcode = writecmd; - brq->data.flags = MMC_DATA_WRITE; - if (brq->mrq.stop) - brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | - MMC_CMD_AC; - } - - if (do_rel_wr) - mmc_apply_rel_rw(brq, card, req); - - /* - * Data tag is used only during writing meta data to speed - * up write and any subsequent read of this meta data - */ - do_data_tag = (card->ext_csd.data_tag_unit_size) && - (req->cmd_flags & REQ_META) && - (rq_data_dir(req) == WRITE) && - ((brq->data.blocks * brq->data.blksz) >= - card->ext_csd.data_tag_unit_size); + brq->cmd.opcode = rq_data_dir(req) == READ ? readcmd : writecmd; /* * Pre-defined multi-block transfers are preferable to @@ -1563,34 +1603,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, brq->mrq.sbc = &brq->sbc; } - mmc_set_data_timeout(&brq->data, card); - - brq->data.sg = mqrq->sg; - brq->data.sg_len = mmc_queue_map_sg(mq, mqrq); - - /* - * Adjust the sg list so it is the same size as the - * request. - */ - if (brq->data.blocks != blk_rq_sectors(req)) { - int i, data_size = brq->data.blocks << 9; - struct scatterlist *sg; - - for_each_sg(brq->data.sg, sg, brq->data.sg_len, i) { - data_size -= sg->length; - if (data_size <= 0) { - sg->length += data_size; - i++; - break; - } - } - brq->data.sg_len = i; - } - - mqrq->areq.mrq = &brq->mrq; mqrq->areq.err_check = mmc_blk_err_check; - - mmc_queue_bounce_pre(mqrq); } static bool mmc_blk_rw_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, From 4b67e63faaa21eab225bc3827a9d06e6ae54bdc7 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:42 +0200 Subject: [PATCH 100/148] mmc: core: Factor out debug prints from mmc_start_request() In preparation to reuse the code for CQE support. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 926e0fde07d7..6b063f0c2553 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -262,26 +262,19 @@ static void __mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) host->ops->request(host, mrq); } -static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) +static void mmc_mrq_pr_debug(struct mmc_host *host, struct mmc_request *mrq) { -#ifdef CONFIG_MMC_DEBUG - unsigned int i, sz; - struct scatterlist *sg; -#endif - mmc_retune_hold(host); - - if (mmc_card_removed(host->card)) - return -ENOMEDIUM; - if (mrq->sbc) { pr_debug("<%s: starting CMD%u arg %08x flags %08x>\n", mmc_hostname(host), mrq->sbc->opcode, mrq->sbc->arg, mrq->sbc->flags); } - pr_debug("%s: starting CMD%u arg %08x flags %08x\n", - mmc_hostname(host), mrq->cmd->opcode, - mrq->cmd->arg, mrq->cmd->flags); + if (mrq->cmd) { + pr_debug("%s: starting CMD%u arg %08x flags %08x\n", + mmc_hostname(host), mrq->cmd->opcode, mrq->cmd->arg, + mrq->cmd->flags); + } if (mrq->data) { pr_debug("%s: blksz %d blocks %d flags %08x " @@ -297,6 +290,20 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mmc_hostname(host), mrq->stop->opcode, mrq->stop->arg, mrq->stop->flags); } +} + +static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) +{ +#ifdef CONFIG_MMC_DEBUG + unsigned int i, sz; + struct scatterlist *sg; +#endif + mmc_retune_hold(host); + + if (mmc_card_removed(host->card)) + return -ENOMEDIUM; + + mmc_mrq_pr_debug(host, mrq); WARN_ON(!host->claimed); From f34bdd2f7e2881090f566a2f93b0d8e1189091e0 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 13 Mar 2017 14:36:43 +0200 Subject: [PATCH 101/148] mmc: core: Factor out mrq preparation from mmc_start_request() In preparation to reuse the code for CQE support. Signed-off-by: Adrian Hunter Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 6b063f0c2553..ffc263283f54 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -292,23 +292,18 @@ static void mmc_mrq_pr_debug(struct mmc_host *host, struct mmc_request *mrq) } } -static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) +static int mmc_mrq_prep(struct mmc_host *host, struct mmc_request *mrq) { #ifdef CONFIG_MMC_DEBUG unsigned int i, sz; struct scatterlist *sg; #endif - mmc_retune_hold(host); - if (mmc_card_removed(host->card)) - return -ENOMEDIUM; - - mmc_mrq_pr_debug(host, mrq); - - WARN_ON(!host->claimed); - - mrq->cmd->error = 0; - mrq->cmd->mrq = mrq; + if (mrq->cmd) { + mrq->cmd->error = 0; + mrq->cmd->mrq = mrq; + mrq->cmd->data = mrq->data; + } if (mrq->sbc) { mrq->sbc->error = 0; mrq->sbc->mrq = mrq; @@ -325,8 +320,6 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) if (sz != mrq->data->blocks * mrq->data->blksz) return -EINVAL; #endif - - mrq->cmd->data = mrq->data; mrq->data->error = 0; mrq->data->mrq = mrq; if (mrq->stop) { @@ -335,6 +328,27 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mrq->stop->mrq = mrq; } } + + return 0; +} + +static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) +{ + int err; + + mmc_retune_hold(host); + + if (mmc_card_removed(host->card)) + return -ENOMEDIUM; + + mmc_mrq_pr_debug(host, mrq); + + WARN_ON(!host->claimed); + + err = mmc_mrq_prep(host, mrq); + if (err) + return err; + led_trigger_event(host->led, LED_FULL); __mmc_start_request(host, mrq); From 5744d50d2d9e7efdae4b5d13cd71f49256ada6ba Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 28 Mar 2017 10:40:29 +0200 Subject: [PATCH 102/148] mmc: core: move some code in mmc_start_areq() "previous" is a better name for the variable storing the previous asynchronous request, better than the opaque name "data" atleast. We see that we assign the return status to the returned variable on all code paths, so we might as well just do that immediately after calling mmc_finalize_areq(). Reviewed-by: Bartlomiej Zolnierkiewicz Signed-off-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index ffc263283f54..4c3e23f310f8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -704,7 +704,7 @@ struct mmc_async_req *mmc_start_areq(struct mmc_host *host, { enum mmc_blk_status status; int start_err = 0; - struct mmc_async_req *data = host->areq; + struct mmc_async_req *previous = host->areq; /* Prepare a new request */ if (areq) @@ -712,13 +712,12 @@ struct mmc_async_req *mmc_start_areq(struct mmc_host *host, /* Finalize previous request */ status = mmc_finalize_areq(host); + if (ret_stat) + *ret_stat = status; /* The previous request is still going on... */ - if (status == MMC_BLK_NEW_REQUEST) { - if (ret_stat) - *ret_stat = status; + if (status == MMC_BLK_NEW_REQUEST) return NULL; - } /* Fine so far, start the new request! */ if (status == MMC_BLK_SUCCESS && areq) @@ -737,9 +736,7 @@ struct mmc_async_req *mmc_start_areq(struct mmc_host *host, else host->areq = areq; - if (ret_stat) - *ret_stat = status; - return data; + return previous; } EXPORT_SYMBOL(mmc_start_areq); From 0e72f95bf329dea7985f0f4ac81cc888e8b79797 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 28 Mar 2017 10:40:30 +0200 Subject: [PATCH 103/148] mmc: core: refactor asynchronous request finalization mmc_wait_for_data_req_done() is called in exactly one place, and having it spread out is making things hard to oversee. Factor this function into mmc_finalize_areq(). Reviewed-by: Bartlomiej Zolnierkiewicz Signed-off-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 86 ++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 53 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 4c3e23f310f8..d6831dc05d98 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -506,56 +506,6 @@ static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) return err; } -/* - * mmc_wait_for_data_req_done() - wait for request completed - * @host: MMC host to prepare the command. - * @mrq: MMC request to wait for - * - * Blocks MMC context till host controller will ack end of data request - * execution or new request notification arrives from the block layer. - * Handles command retries. - * - * Returns enum mmc_blk_status after checking errors. - */ -static enum mmc_blk_status mmc_wait_for_data_req_done(struct mmc_host *host, - struct mmc_request *mrq) -{ - struct mmc_command *cmd; - struct mmc_context_info *context_info = &host->context_info; - enum mmc_blk_status status; - - while (1) { - wait_event_interruptible(context_info->wait, - (context_info->is_done_rcv || - context_info->is_new_req)); - - if (context_info->is_done_rcv) { - context_info->is_done_rcv = false; - cmd = mrq->cmd; - - if (!cmd->error || !cmd->retries || - mmc_card_removed(host->card)) { - status = host->areq->err_check(host->card, - host->areq); - break; /* return status */ - } else { - mmc_retune_recheck(host); - pr_info("%s: req failed (CMD%u): %d, retrying...\n", - mmc_hostname(host), - cmd->opcode, cmd->error); - cmd->retries--; - cmd->error = 0; - __mmc_start_request(host, mrq); - continue; /* wait for done/new event again */ - } - } - - return MMC_BLK_NEW_REQUEST; - } - mmc_retune_release(host); - return status; -} - void mmc_wait_for_req_done(struct mmc_host *host, struct mmc_request *mrq) { struct mmc_command *cmd; @@ -660,14 +610,44 @@ static void mmc_post_req(struct mmc_host *host, struct mmc_request *mrq, */ static enum mmc_blk_status mmc_finalize_areq(struct mmc_host *host) { + struct mmc_context_info *context_info = &host->context_info; enum mmc_blk_status status; if (!host->areq) return MMC_BLK_SUCCESS; - status = mmc_wait_for_data_req_done(host, host->areq->mrq); - if (status == MMC_BLK_NEW_REQUEST) - return status; + while (1) { + wait_event_interruptible(context_info->wait, + (context_info->is_done_rcv || + context_info->is_new_req)); + + if (context_info->is_done_rcv) { + struct mmc_command *cmd; + + context_info->is_done_rcv = false; + cmd = host->areq->mrq->cmd; + + if (!cmd->error || !cmd->retries || + mmc_card_removed(host->card)) { + status = host->areq->err_check(host->card, + host->areq); + break; /* return status */ + } else { + mmc_retune_recheck(host); + pr_info("%s: req failed (CMD%u): %d, retrying...\n", + mmc_hostname(host), + cmd->opcode, cmd->error); + cmd->retries--; + cmd->error = 0; + __mmc_start_request(host, host->areq->mrq); + continue; /* wait for done/new event again */ + } + } + + return MMC_BLK_NEW_REQUEST; + } + + mmc_retune_release(host); /* * Check BKOPS urgency for each R1 response From 67b8360acc0d717c46af3008fc10dcc5c91f7745 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 28 Mar 2017 10:40:31 +0200 Subject: [PATCH 104/148] mmc: core: refactor mmc_request_done() We have this construction: if (a && b && !c) finalize; else block; finalize; Which is equivalent by boolean logic to: if (!a || !b || c) block; finalize; Which is simpler code. Reviewed-by: Bartlomiej Zolnierkiewicz Signed-off-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d6831dc05d98..0bb39795d484 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -172,14 +172,16 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) trace_mmc_request_done(host, mrq); - if (err && cmd->retries && !mmc_card_removed(host->card)) { - /* - * Request starter must handle retries - see - * mmc_wait_for_req_done(). - */ - if (mrq->done) - mrq->done(mrq); - } else { + /* + * We list various conditions for the command to be considered + * properly done: + * + * - There was no error, OK fine then + * - We are not doing some kind of retry + * - The card was removed (...so just complete everything no matter + * if there are errors or retries) + */ + if (!err || !cmd->retries || mmc_card_removed(host->card)) { mmc_should_fail_request(host, mrq); if (!host->ongoing_mrq) @@ -211,10 +213,13 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) mrq->stop->resp[0], mrq->stop->resp[1], mrq->stop->resp[2], mrq->stop->resp[3]); } - - if (mrq->done) - mrq->done(mrq); } + /* + * Request starter must handle retries - see + * mmc_wait_for_req_done(). + */ + if (mrq->done) + mrq->done(mrq); } EXPORT_SYMBOL(mmc_request_done); From dc5aee3f9f454c35d4e092873b103dc791119e0e Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 30 Mar 2017 17:31:23 +0200 Subject: [PATCH 105/148] dt-bindings: mmc: Add Cavium SOCs MMC bindings Add description of Cavium Octeon and ThunderX SOC device tree bindings. CC: Ulf Hansson CC: Rob Herring CC: Mark Rutland CC: devicetree@vger.kernel.org Signed-off-by: Jan Glauber Signed-off-by: David Daney Signed-off-by: Steven J. Hill Acked-by: Rob Herring Signed-off-by: Ulf Hansson --- .../devicetree/bindings/mmc/cavium-mmc.txt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/cavium-mmc.txt diff --git a/Documentation/devicetree/bindings/mmc/cavium-mmc.txt b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt new file mode 100644 index 000000000000..1433e6201dff --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt @@ -0,0 +1,57 @@ +* Cavium Octeon & ThunderX MMC controller + +The highspeed MMC host controller on Caviums SoCs provides an interface +for MMC and SD types of memory cards. + +Supported maximum speeds are the ones of the eMMC standard 4.41 as well +as the speed of SD standard 4.0. Only 3.3 Volt is supported. + +Required properties: + - compatible : should be one of: + cavium,octeon-6130-mmc + cavium,octeon-7890-mmc + cavium,thunder-8190-mmc + cavium,thunder-8390-mmc + mmc-slot + - reg : mmc controller base registers + - clocks : phandle + +Optional properties: + - for cd, bus-width and additional generic mmc parameters + please refer to mmc.txt within this directory + - cavium,cmd-clk-skew : number of coprocessor clocks before sampling command + - cavium,dat-clk-skew : number of coprocessor clocks before sampling data + +Deprecated properties: +- spi-max-frequency : use max-frequency instead +- cavium,bus-max-width : use bus-width instead +- power-gpios : use vmmc-supply instead +- cavium,octeon-6130-mmc-slot : use mmc-slot instead + +Examples: + mmc_1_4: mmc@1,4 { + compatible = "cavium,thunder-8390-mmc"; + reg = <0x0c00 0 0 0 0>; /* DEVFN = 0x0c (1:4) */ + #address-cells = <1>; + #size-cells = <0>; + clocks = <&sclk>; + + mmc-slot@0 { + compatible = "mmc-slot"; + reg = <0>; + vmmc-supply = <&mmc_supply_3v3>; + max-frequency = <42000000>; + bus-width = <4>; + cap-sd-highspeed; + }; + + mmc-slot@1 { + compatible = "mmc-slot"; + reg = <1>; + vmmc-supply = <&mmc_supply_3v3>; + max-frequency = <42000000>; + bus-width = <8>; + cap-mmc-highspeed; + non-removable; + }; + }; From ba3869ff32e4a671b0f9baa73900f9e2bfeb0414 Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 30 Mar 2017 17:31:24 +0200 Subject: [PATCH 106/148] mmc: cavium: Add core MMC driver for Cavium SOCs This core driver will be used by a MIPS platform driver or by an ARM64 PCI driver. The core driver implements the mmc_host_ops and slot probe & remove functions. Callbacks are provided to allow platform specific interrupt enable and bus locking. The host controller supports: - up to 4 slots that can contain sd-cards or eMMC chips - 1, 4 and 8 bit bus width - SDR and DDR - transfers up to 52 Mhz (might be less when multiple slots are used) - DMA read/write - multi-block read/write (but not stream mode) Voltage is limited to 3.3v and shared for all slots (vmmc and vmmcq). A global lock for all MMC devices is required because the host controller is shared. Signed-off-by: Jan Glauber Signed-off-by: David Daney Signed-off-by: Steven J. Hill Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium.c | 982 ++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/cavium.h | 192 ++++++++ 2 files changed, 1174 insertions(+) create mode 100644 drivers/mmc/host/cavium.c create mode 100644 drivers/mmc/host/cavium.h diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c new file mode 100644 index 000000000000..910e290579bf --- /dev/null +++ b/drivers/mmc/host/cavium.c @@ -0,0 +1,982 @@ +/* + * Shared part of driver for MMC/SDHC controller on Cavium OCTEON and + * ThunderX SOCs. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2012-2017 Cavium Inc. + * Authors: + * David Daney + * Peter Swain + * Steven J. Hill + * Jan Glauber + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cavium.h" + +const char *cvm_mmc_irq_names[] = { + "MMC Buffer", + "MMC Command", + "MMC DMA", + "MMC Command Error", + "MMC DMA Error", + "MMC Switch", + "MMC Switch Error", + "MMC DMA int Fifo", + "MMC DMA int", +}; + +/* + * The Cavium MMC host hardware assumes that all commands have fixed + * command and response types. These are correct if MMC devices are + * being used. However, non-MMC devices like SD use command and + * response types that are unexpected by the host hardware. + * + * The command and response types can be overridden by supplying an + * XOR value that is applied to the type. We calculate the XOR value + * from the values in this table and the flags passed from the MMC + * core. + */ +static struct cvm_mmc_cr_type cvm_mmc_cr_types[] = { + {0, 0}, /* CMD0 */ + {0, 3}, /* CMD1 */ + {0, 2}, /* CMD2 */ + {0, 1}, /* CMD3 */ + {0, 0}, /* CMD4 */ + {0, 1}, /* CMD5 */ + {0, 1}, /* CMD6 */ + {0, 1}, /* CMD7 */ + {1, 1}, /* CMD8 */ + {0, 2}, /* CMD9 */ + {0, 2}, /* CMD10 */ + {1, 1}, /* CMD11 */ + {0, 1}, /* CMD12 */ + {0, 1}, /* CMD13 */ + {1, 1}, /* CMD14 */ + {0, 0}, /* CMD15 */ + {0, 1}, /* CMD16 */ + {1, 1}, /* CMD17 */ + {1, 1}, /* CMD18 */ + {3, 1}, /* CMD19 */ + {2, 1}, /* CMD20 */ + {0, 0}, /* CMD21 */ + {0, 0}, /* CMD22 */ + {0, 1}, /* CMD23 */ + {2, 1}, /* CMD24 */ + {2, 1}, /* CMD25 */ + {2, 1}, /* CMD26 */ + {2, 1}, /* CMD27 */ + {0, 1}, /* CMD28 */ + {0, 1}, /* CMD29 */ + {1, 1}, /* CMD30 */ + {1, 1}, /* CMD31 */ + {0, 0}, /* CMD32 */ + {0, 0}, /* CMD33 */ + {0, 0}, /* CMD34 */ + {0, 1}, /* CMD35 */ + {0, 1}, /* CMD36 */ + {0, 0}, /* CMD37 */ + {0, 1}, /* CMD38 */ + {0, 4}, /* CMD39 */ + {0, 5}, /* CMD40 */ + {0, 0}, /* CMD41 */ + {2, 1}, /* CMD42 */ + {0, 0}, /* CMD43 */ + {0, 0}, /* CMD44 */ + {0, 0}, /* CMD45 */ + {0, 0}, /* CMD46 */ + {0, 0}, /* CMD47 */ + {0, 0}, /* CMD48 */ + {0, 0}, /* CMD49 */ + {0, 0}, /* CMD50 */ + {0, 0}, /* CMD51 */ + {0, 0}, /* CMD52 */ + {0, 0}, /* CMD53 */ + {0, 0}, /* CMD54 */ + {0, 1}, /* CMD55 */ + {0xff, 0xff}, /* CMD56 */ + {0, 0}, /* CMD57 */ + {0, 0}, /* CMD58 */ + {0, 0}, /* CMD59 */ + {0, 0}, /* CMD60 */ + {0, 0}, /* CMD61 */ + {0, 0}, /* CMD62 */ + {0, 0} /* CMD63 */ +}; + +static struct cvm_mmc_cr_mods cvm_mmc_get_cr_mods(struct mmc_command *cmd) +{ + struct cvm_mmc_cr_type *cr; + u8 hardware_ctype, hardware_rtype; + u8 desired_ctype = 0, desired_rtype = 0; + struct cvm_mmc_cr_mods r; + + cr = cvm_mmc_cr_types + (cmd->opcode & 0x3f); + hardware_ctype = cr->ctype; + hardware_rtype = cr->rtype; + if (cmd->opcode == MMC_GEN_CMD) + hardware_ctype = (cmd->arg & 1) ? 1 : 2; + + switch (mmc_cmd_type(cmd)) { + case MMC_CMD_ADTC: + desired_ctype = (cmd->data->flags & MMC_DATA_WRITE) ? 2 : 1; + break; + case MMC_CMD_AC: + case MMC_CMD_BC: + case MMC_CMD_BCR: + desired_ctype = 0; + break; + } + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_NONE: + desired_rtype = 0; + break; + case MMC_RSP_R1:/* MMC_RSP_R5, MMC_RSP_R6, MMC_RSP_R7 */ + case MMC_RSP_R1B: + desired_rtype = 1; + break; + case MMC_RSP_R2: + desired_rtype = 2; + break; + case MMC_RSP_R3: /* MMC_RSP_R4 */ + desired_rtype = 3; + break; + } + r.ctype_xor = desired_ctype ^ hardware_ctype; + r.rtype_xor = desired_rtype ^ hardware_rtype; + return r; +} + +static void check_switch_errors(struct cvm_mmc_host *host) +{ + u64 emm_switch; + + emm_switch = readq(host->base + MIO_EMM_SWITCH(host)); + if (emm_switch & MIO_EMM_SWITCH_ERR0) + dev_err(host->dev, "Switch power class error\n"); + if (emm_switch & MIO_EMM_SWITCH_ERR1) + dev_err(host->dev, "Switch hs timing error\n"); + if (emm_switch & MIO_EMM_SWITCH_ERR2) + dev_err(host->dev, "Switch bus width error\n"); +} + +static void clear_bus_id(u64 *reg) +{ + u64 bus_id_mask = GENMASK_ULL(61, 60); + + *reg &= ~bus_id_mask; +} + +static void set_bus_id(u64 *reg, int bus_id) +{ + clear_bus_id(reg); + *reg |= FIELD_PREP(GENMASK(61, 60), bus_id); +} + +static int get_bus_id(u64 reg) +{ + return FIELD_GET(GENMASK_ULL(61, 60), reg); +} + +/* + * We never set the switch_exe bit since that would interfere + * with the commands send by the MMC core. + */ +static void do_switch(struct cvm_mmc_host *host, u64 emm_switch) +{ + int retries = 100; + u64 rsp_sts; + int bus_id; + + /* + * Modes setting only taken from slot 0. Work around that hardware + * issue by first switching to slot 0. + */ + bus_id = get_bus_id(emm_switch); + clear_bus_id(&emm_switch); + writeq(emm_switch, host->base + MIO_EMM_SWITCH(host)); + + set_bus_id(&emm_switch, bus_id); + writeq(emm_switch, host->base + MIO_EMM_SWITCH(host)); + + /* wait for the switch to finish */ + do { + rsp_sts = readq(host->base + MIO_EMM_RSP_STS(host)); + if (!(rsp_sts & MIO_EMM_RSP_STS_SWITCH_VAL)) + break; + udelay(10); + } while (--retries); + + check_switch_errors(host); +} + +static bool switch_val_changed(struct cvm_mmc_slot *slot, u64 new_val) +{ + /* Match BUS_ID, HS_TIMING, BUS_WIDTH, POWER_CLASS, CLK_HI, CLK_LO */ + u64 match = 0x3001070fffffffffull; + + return (slot->cached_switch & match) != (new_val & match); +} + +static void set_wdog(struct cvm_mmc_slot *slot, unsigned int ns) +{ + u64 timeout; + + if (!slot->clock) + return; + + if (ns) + timeout = (slot->clock * ns) / NSEC_PER_SEC; + else + timeout = (slot->clock * 850ull) / 1000ull; + writeq(timeout, slot->host->base + MIO_EMM_WDOG(slot->host)); +} + +static void cvm_mmc_reset_bus(struct cvm_mmc_slot *slot) +{ + struct cvm_mmc_host *host = slot->host; + u64 emm_switch, wdog; + + emm_switch = readq(slot->host->base + MIO_EMM_SWITCH(host)); + emm_switch &= ~(MIO_EMM_SWITCH_EXE | MIO_EMM_SWITCH_ERR0 | + MIO_EMM_SWITCH_ERR1 | MIO_EMM_SWITCH_ERR2); + set_bus_id(&emm_switch, slot->bus_id); + + wdog = readq(slot->host->base + MIO_EMM_WDOG(host)); + do_switch(slot->host, emm_switch); + + slot->cached_switch = emm_switch; + + msleep(20); + + writeq(wdog, slot->host->base + MIO_EMM_WDOG(host)); +} + +/* Switch to another slot if needed */ +static void cvm_mmc_switch_to(struct cvm_mmc_slot *slot) +{ + struct cvm_mmc_host *host = slot->host; + struct cvm_mmc_slot *old_slot; + u64 emm_sample, emm_switch; + + if (slot->bus_id == host->last_slot) + return; + + if (host->last_slot >= 0 && host->slot[host->last_slot]) { + old_slot = host->slot[host->last_slot]; + old_slot->cached_switch = readq(host->base + MIO_EMM_SWITCH(host)); + old_slot->cached_rca = readq(host->base + MIO_EMM_RCA(host)); + } + + writeq(slot->cached_rca, host->base + MIO_EMM_RCA(host)); + emm_switch = slot->cached_switch; + set_bus_id(&emm_switch, slot->bus_id); + do_switch(host, emm_switch); + + emm_sample = FIELD_PREP(MIO_EMM_SAMPLE_CMD_CNT, slot->cmd_cnt) | + FIELD_PREP(MIO_EMM_SAMPLE_DAT_CNT, slot->dat_cnt); + writeq(emm_sample, host->base + MIO_EMM_SAMPLE(host)); + + host->last_slot = slot->bus_id; +} + +static void do_read(struct cvm_mmc_host *host, struct mmc_request *req, + u64 dbuf) +{ + struct sg_mapping_iter *smi = &host->smi; + int data_len = req->data->blocks * req->data->blksz; + int bytes_xfered, shift = -1; + u64 dat = 0; + + /* Auto inc from offset zero */ + writeq((0x10000 | (dbuf << 6)), host->base + MIO_EMM_BUF_IDX(host)); + + for (bytes_xfered = 0; bytes_xfered < data_len;) { + if (smi->consumed >= smi->length) { + if (!sg_miter_next(smi)) + break; + smi->consumed = 0; + } + + if (shift < 0) { + dat = readq(host->base + MIO_EMM_BUF_DAT(host)); + shift = 56; + } + + while (smi->consumed < smi->length && shift >= 0) { + ((u8 *)smi->addr)[smi->consumed] = (dat >> shift) & 0xff; + bytes_xfered++; + smi->consumed++; + shift -= 8; + } + } + + sg_miter_stop(smi); + req->data->bytes_xfered = bytes_xfered; + req->data->error = 0; +} + +static void do_write(struct mmc_request *req) +{ + req->data->bytes_xfered = req->data->blocks * req->data->blksz; + req->data->error = 0; +} + +static void set_cmd_response(struct cvm_mmc_host *host, struct mmc_request *req, + u64 rsp_sts) +{ + u64 rsp_hi, rsp_lo; + + if (!(rsp_sts & MIO_EMM_RSP_STS_RSP_VAL)) + return; + + rsp_lo = readq(host->base + MIO_EMM_RSP_LO(host)); + + switch (FIELD_GET(MIO_EMM_RSP_STS_RSP_TYPE, rsp_sts)) { + case 1: + case 3: + req->cmd->resp[0] = (rsp_lo >> 8) & 0xffffffff; + req->cmd->resp[1] = 0; + req->cmd->resp[2] = 0; + req->cmd->resp[3] = 0; + break; + case 2: + req->cmd->resp[3] = rsp_lo & 0xffffffff; + req->cmd->resp[2] = (rsp_lo >> 32) & 0xffffffff; + rsp_hi = readq(host->base + MIO_EMM_RSP_HI(host)); + req->cmd->resp[1] = rsp_hi & 0xffffffff; + req->cmd->resp[0] = (rsp_hi >> 32) & 0xffffffff; + break; + } +} + +static int get_dma_dir(struct mmc_data *data) +{ + return (data->flags & MMC_DATA_WRITE) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; +} + +static int finish_dma_single(struct cvm_mmc_host *host, struct mmc_data *data) +{ + data->bytes_xfered = data->blocks * data->blksz; + data->error = 0; + return 1; +} + +static int finish_dma(struct cvm_mmc_host *host, struct mmc_data *data) +{ + return finish_dma_single(host, data); +} + +static int check_status(u64 rsp_sts) +{ + if (rsp_sts & MIO_EMM_RSP_STS_RSP_BAD_STS || + rsp_sts & MIO_EMM_RSP_STS_RSP_CRC_ERR || + rsp_sts & MIO_EMM_RSP_STS_BLK_CRC_ERR) + return -EILSEQ; + if (rsp_sts & MIO_EMM_RSP_STS_RSP_TIMEOUT || + rsp_sts & MIO_EMM_RSP_STS_BLK_TIMEOUT) + return -ETIMEDOUT; + if (rsp_sts & MIO_EMM_RSP_STS_DBUF_ERR) + return -EIO; + return 0; +} + +/* Try to clean up failed DMA. */ +static void cleanup_dma(struct cvm_mmc_host *host, u64 rsp_sts) +{ + u64 emm_dma; + + emm_dma = readq(host->base + MIO_EMM_DMA(host)); + emm_dma |= FIELD_PREP(MIO_EMM_DMA_VAL, 1) | + FIELD_PREP(MIO_EMM_DMA_DAT_NULL, 1); + set_bus_id(&emm_dma, get_bus_id(rsp_sts)); + writeq(emm_dma, host->base + MIO_EMM_DMA(host)); +} + +irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id) +{ + struct cvm_mmc_host *host = dev_id; + struct mmc_request *req; + unsigned long flags = 0; + u64 emm_int, rsp_sts; + bool host_done; + + if (host->need_irq_handler_lock) + spin_lock_irqsave(&host->irq_handler_lock, flags); + else + __acquire(&host->irq_handler_lock); + + /* Clear interrupt bits (write 1 clears ). */ + emm_int = readq(host->base + MIO_EMM_INT(host)); + writeq(emm_int, host->base + MIO_EMM_INT(host)); + + if (emm_int & MIO_EMM_INT_SWITCH_ERR) + check_switch_errors(host); + + req = host->current_req; + if (!req) + goto out; + + rsp_sts = readq(host->base + MIO_EMM_RSP_STS(host)); + /* + * dma_val set means DMA is still in progress. Don't touch + * the request and wait for the interrupt indicating that + * the DMA is finished. + */ + if ((rsp_sts & MIO_EMM_RSP_STS_DMA_VAL) && host->dma_active) + goto out; + + if (!host->dma_active && req->data && + (emm_int & MIO_EMM_INT_BUF_DONE)) { + unsigned int type = (rsp_sts >> 7) & 3; + + if (type == 1) + do_read(host, req, rsp_sts & MIO_EMM_RSP_STS_DBUF); + else if (type == 2) + do_write(req); + } + + host_done = emm_int & MIO_EMM_INT_CMD_DONE || + emm_int & MIO_EMM_INT_DMA_DONE || + emm_int & MIO_EMM_INT_CMD_ERR || + emm_int & MIO_EMM_INT_DMA_ERR; + + if (!(host_done && req->done)) + goto no_req_done; + + req->cmd->error = check_status(rsp_sts); + + if (host->dma_active && req->data) + if (!finish_dma(host, req->data)) + goto no_req_done; + + set_cmd_response(host, req, rsp_sts); + if ((emm_int & MIO_EMM_INT_DMA_ERR) && + (rsp_sts & MIO_EMM_RSP_STS_DMA_PEND)) + cleanup_dma(host, rsp_sts); + + host->current_req = NULL; + req->done(req); + +no_req_done: + if (host->dmar_fixup_done) + host->dmar_fixup_done(host); + if (host_done) + host->release_bus(host); +out: + if (host->need_irq_handler_lock) + spin_unlock_irqrestore(&host->irq_handler_lock, flags); + else + __release(&host->irq_handler_lock); + return IRQ_RETVAL(emm_int != 0); +} + +/* + * Program DMA_CFG and if needed DMA_ADR. + * Returns 0 on error, DMA address otherwise. + */ +static u64 prepare_dma_single(struct cvm_mmc_host *host, struct mmc_data *data) +{ + u64 dma_cfg, addr; + int count, rw; + + count = dma_map_sg(host->dev, data->sg, data->sg_len, + get_dma_dir(data)); + if (!count) + return 0; + + rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0; + dma_cfg = FIELD_PREP(MIO_EMM_DMA_CFG_EN, 1) | + FIELD_PREP(MIO_EMM_DMA_CFG_RW, rw); +#ifdef __LITTLE_ENDIAN + dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_ENDIAN, 1); +#endif + dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_SIZE, + (sg_dma_len(&data->sg[0]) / 8) - 1); + + addr = sg_dma_address(&data->sg[0]); + if (!host->big_dma_addr) + dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_ADR, addr); + writeq(dma_cfg, host->dma_base + MIO_EMM_DMA_CFG(host)); + + pr_debug("[%s] sg_dma_len: %u total sg_elem: %d\n", + (rw) ? "W" : "R", sg_dma_len(&data->sg[0]), count); + + if (host->big_dma_addr) + writeq(addr, host->dma_base + MIO_EMM_DMA_ADR(host)); + return addr; +} + +static u64 prepare_dma(struct cvm_mmc_host *host, struct mmc_data *data) +{ + return prepare_dma_single(host, data); +} + +static u64 prepare_ext_dma(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct cvm_mmc_slot *slot = mmc_priv(mmc); + u64 emm_dma; + + emm_dma = FIELD_PREP(MIO_EMM_DMA_VAL, 1) | + FIELD_PREP(MIO_EMM_DMA_SECTOR, + (mrq->data->blksz == 512) ? 1 : 0) | + FIELD_PREP(MIO_EMM_DMA_RW, + (mrq->data->flags & MMC_DATA_WRITE) ? 1 : 0) | + FIELD_PREP(MIO_EMM_DMA_BLOCK_CNT, mrq->data->blocks) | + FIELD_PREP(MIO_EMM_DMA_CARD_ADDR, mrq->cmd->arg); + set_bus_id(&emm_dma, slot->bus_id); + + if (mmc_card_mmc(mmc->card) || (mmc_card_sd(mmc->card) && + (mmc->card->scr.cmds & SD_SCR_CMD23_SUPPORT))) + emm_dma |= FIELD_PREP(MIO_EMM_DMA_MULTI, 1); + + pr_debug("[%s] blocks: %u multi: %d\n", + (emm_dma & MIO_EMM_DMA_RW) ? "W" : "R", + mrq->data->blocks, (emm_dma & MIO_EMM_DMA_MULTI) ? 1 : 0); + return emm_dma; +} + +static void cvm_mmc_dma_request(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + struct cvm_mmc_slot *slot = mmc_priv(mmc); + struct cvm_mmc_host *host = slot->host; + struct mmc_data *data; + u64 emm_dma, addr; + + if (!mrq->data || !mrq->data->sg || !mrq->data->sg_len || + !mrq->stop || mrq->stop->opcode != MMC_STOP_TRANSMISSION) { + dev_err(&mmc->card->dev, + "Error: cmv_mmc_dma_request no data\n"); + goto error; + } + + cvm_mmc_switch_to(slot); + + data = mrq->data; + pr_debug("DMA request blocks: %d block_size: %d total_size: %d\n", + data->blocks, data->blksz, data->blocks * data->blksz); + if (data->timeout_ns) + set_wdog(slot, data->timeout_ns); + + WARN_ON(host->current_req); + host->current_req = mrq; + + emm_dma = prepare_ext_dma(mmc, mrq); + addr = prepare_dma(host, data); + if (!addr) { + dev_err(host->dev, "prepare_dma failed\n"); + goto error; + } + + host->dma_active = true; + host->int_enable(host, MIO_EMM_INT_CMD_ERR | MIO_EMM_INT_DMA_DONE | + MIO_EMM_INT_DMA_ERR); + + if (host->dmar_fixup) + host->dmar_fixup(host, mrq->cmd, data, addr); + + /* + * If we have a valid SD card in the slot, we set the response + * bit mask to check for CRC errors and timeouts only. + * Otherwise, use the default power reset value. + */ + if (mmc->card && mmc_card_sd(mmc->card)) + writeq(0x00b00000ull, host->base + MIO_EMM_STS_MASK(host)); + else + writeq(0xe4390080ull, host->base + MIO_EMM_STS_MASK(host)); + writeq(emm_dma, host->base + MIO_EMM_DMA(host)); + return; + +error: + mrq->cmd->error = -EINVAL; + if (mrq->done) + mrq->done(mrq); + host->release_bus(host); +} + +static void do_read_request(struct cvm_mmc_host *host, struct mmc_request *mrq) +{ + sg_miter_start(&host->smi, mrq->data->sg, mrq->data->sg_len, + SG_MITER_ATOMIC | SG_MITER_TO_SG); +} + +static void do_write_request(struct cvm_mmc_host *host, struct mmc_request *mrq) +{ + unsigned int data_len = mrq->data->blocks * mrq->data->blksz; + struct sg_mapping_iter *smi = &host->smi; + unsigned int bytes_xfered; + int shift = 56; + u64 dat = 0; + + /* Copy data to the xmit buffer before issuing the command. */ + sg_miter_start(smi, mrq->data->sg, mrq->data->sg_len, SG_MITER_FROM_SG); + + /* Auto inc from offset zero, dbuf zero */ + writeq(0x10000ull, host->base + MIO_EMM_BUF_IDX(host)); + + for (bytes_xfered = 0; bytes_xfered < data_len;) { + if (smi->consumed >= smi->length) { + if (!sg_miter_next(smi)) + break; + smi->consumed = 0; + } + + while (smi->consumed < smi->length && shift >= 0) { + dat |= ((u8 *)smi->addr)[smi->consumed] << shift; + bytes_xfered++; + smi->consumed++; + shift -= 8; + } + + if (shift < 0) { + writeq(dat, host->base + MIO_EMM_BUF_DAT(host)); + shift = 56; + dat = 0; + } + } + sg_miter_stop(smi); +} + +static void cvm_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct cvm_mmc_slot *slot = mmc_priv(mmc); + struct cvm_mmc_host *host = slot->host; + struct mmc_command *cmd = mrq->cmd; + struct cvm_mmc_cr_mods mods; + u64 emm_cmd, rsp_sts; + int retries = 100; + + /* + * Note about locking: + * All MMC devices share the same bus and controller. Allow only a + * single user of the bootbus/MMC bus at a time. The lock is acquired + * on all entry points from the MMC layer. + * + * For requests the lock is only released after the completion + * interrupt! + */ + host->acquire_bus(host); + + if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK || + cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) + return cvm_mmc_dma_request(mmc, mrq); + + cvm_mmc_switch_to(slot); + + mods = cvm_mmc_get_cr_mods(cmd); + + WARN_ON(host->current_req); + host->current_req = mrq; + + if (cmd->data) { + if (cmd->data->flags & MMC_DATA_READ) + do_read_request(host, mrq); + else + do_write_request(host, mrq); + + if (cmd->data->timeout_ns) + set_wdog(slot, cmd->data->timeout_ns); + } else + set_wdog(slot, 0); + + host->dma_active = false; + host->int_enable(host, MIO_EMM_INT_CMD_DONE | MIO_EMM_INT_CMD_ERR); + + emm_cmd = FIELD_PREP(MIO_EMM_CMD_VAL, 1) | + FIELD_PREP(MIO_EMM_CMD_CTYPE_XOR, mods.ctype_xor) | + FIELD_PREP(MIO_EMM_CMD_RTYPE_XOR, mods.rtype_xor) | + FIELD_PREP(MIO_EMM_CMD_IDX, cmd->opcode) | + FIELD_PREP(MIO_EMM_CMD_ARG, cmd->arg); + set_bus_id(&emm_cmd, slot->bus_id); + if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) + emm_cmd |= FIELD_PREP(MIO_EMM_CMD_OFFSET, + 64 - ((cmd->data->blocks * cmd->data->blksz) / 8)); + + writeq(0, host->base + MIO_EMM_STS_MASK(host)); + +retry: + rsp_sts = readq(host->base + MIO_EMM_RSP_STS(host)); + if (rsp_sts & MIO_EMM_RSP_STS_DMA_VAL || + rsp_sts & MIO_EMM_RSP_STS_CMD_VAL || + rsp_sts & MIO_EMM_RSP_STS_SWITCH_VAL || + rsp_sts & MIO_EMM_RSP_STS_DMA_PEND) { + udelay(10); + if (--retries) + goto retry; + } + if (!retries) + dev_err(host->dev, "Bad status: %llx before command write\n", rsp_sts); + writeq(emm_cmd, host->base + MIO_EMM_CMD(host)); +} + +static void cvm_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct cvm_mmc_slot *slot = mmc_priv(mmc); + struct cvm_mmc_host *host = slot->host; + int clk_period = 0, power_class = 10, bus_width = 0; + u64 clock, emm_switch; + + host->acquire_bus(host); + cvm_mmc_switch_to(slot); + + /* Set the power state */ + switch (ios->power_mode) { + case MMC_POWER_ON: + break; + + case MMC_POWER_OFF: + cvm_mmc_reset_bus(slot); + if (host->global_pwr_gpiod) + host->set_shared_power(host, 0); + else + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); + break; + + case MMC_POWER_UP: + if (host->global_pwr_gpiod) + host->set_shared_power(host, 1); + else + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd); + break; + } + + /* Convert bus width to HW definition */ + switch (ios->bus_width) { + case MMC_BUS_WIDTH_8: + bus_width = 2; + break; + case MMC_BUS_WIDTH_4: + bus_width = 1; + break; + case MMC_BUS_WIDTH_1: + bus_width = 0; + break; + } + + /* Change the clock frequency. */ + clock = ios->clock; + if (clock > 52000000) + clock = 52000000; + slot->clock = clock; + + if (clock) + clk_period = (host->sys_freq + clock - 1) / (2 * clock); + + emm_switch = FIELD_PREP(MIO_EMM_SWITCH_HS_TIMING, + (ios->timing == MMC_TIMING_MMC_HS)) | + FIELD_PREP(MIO_EMM_SWITCH_BUS_WIDTH, bus_width) | + FIELD_PREP(MIO_EMM_SWITCH_POWER_CLASS, power_class) | + FIELD_PREP(MIO_EMM_SWITCH_CLK_HI, clk_period) | + FIELD_PREP(MIO_EMM_SWITCH_CLK_LO, clk_period); + set_bus_id(&emm_switch, slot->bus_id); + + if (!switch_val_changed(slot, emm_switch)) + goto out; + + set_wdog(slot, 0); + do_switch(host, emm_switch); + slot->cached_switch = emm_switch; +out: + host->release_bus(host); +} + +static const struct mmc_host_ops cvm_mmc_ops = { + .request = cvm_mmc_request, + .set_ios = cvm_mmc_set_ios, + .get_ro = mmc_gpio_get_ro, + .get_cd = mmc_gpio_get_cd, +}; + +static void cvm_mmc_set_clock(struct cvm_mmc_slot *slot, unsigned int clock) +{ + struct mmc_host *mmc = slot->mmc; + + clock = min(clock, mmc->f_max); + clock = max(clock, mmc->f_min); + slot->clock = clock; +} + +static int cvm_mmc_init_lowlevel(struct cvm_mmc_slot *slot) +{ + struct cvm_mmc_host *host = slot->host; + u64 emm_switch; + + /* Enable this bus slot. */ + host->emm_cfg |= (1ull << slot->bus_id); + writeq(host->emm_cfg, slot->host->base + MIO_EMM_CFG(host)); + udelay(10); + + /* Program initial clock speed and power. */ + cvm_mmc_set_clock(slot, slot->mmc->f_min); + emm_switch = FIELD_PREP(MIO_EMM_SWITCH_POWER_CLASS, 10); + emm_switch |= FIELD_PREP(MIO_EMM_SWITCH_CLK_HI, + (host->sys_freq / slot->clock) / 2); + emm_switch |= FIELD_PREP(MIO_EMM_SWITCH_CLK_LO, + (host->sys_freq / slot->clock) / 2); + + /* Make the changes take effect on this bus slot. */ + set_bus_id(&emm_switch, slot->bus_id); + do_switch(host, emm_switch); + + slot->cached_switch = emm_switch; + + /* + * Set watchdog timeout value and default reset value + * for the mask register. Finally, set the CARD_RCA + * bit so that we can get the card address relative + * to the CMD register for CMD7 transactions. + */ + set_wdog(slot, 0); + writeq(0xe4390080ull, host->base + MIO_EMM_STS_MASK(host)); + writeq(1, host->base + MIO_EMM_RCA(host)); + return 0; +} + +static int cvm_mmc_of_parse(struct device *dev, struct cvm_mmc_slot *slot) +{ + u32 id, cmd_skew = 0, dat_skew = 0, bus_width = 0; + struct device_node *node = dev->of_node; + struct mmc_host *mmc = slot->mmc; + u64 clock_period; + int ret; + + ret = of_property_read_u32(node, "reg", &id); + if (ret) { + dev_err(dev, "Missing or invalid reg property on %s\n", + of_node_full_name(node)); + return ret; + } + + if (id >= CAVIUM_MAX_MMC || slot->host->slot[id]) { + dev_err(dev, "Invalid reg property on %s\n", + of_node_full_name(node)); + return -EINVAL; + } + + mmc->supply.vmmc = devm_regulator_get_optional(dev, "vmmc"); + if (IS_ERR(mmc->supply.vmmc)) { + if (PTR_ERR(mmc->supply.vmmc) == -EPROBE_DEFER) + return -EPROBE_DEFER; + /* + * Legacy Octeon firmware has no regulator entry, fall-back to + * a hard-coded voltage to get a sane OCR. + */ + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + } else { + ret = mmc_regulator_get_ocrmask(mmc->supply.vmmc); + if (ret > 0) + mmc->ocr_avail = ret; + } + + /* Common MMC bindings */ + ret = mmc_of_parse(mmc); + if (ret) + return ret; + + /* Set bus width */ + if (!(mmc->caps & (MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA))) { + of_property_read_u32(node, "cavium,bus-max-width", &bus_width); + if (bus_width == 8) + mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA; + else if (bus_width == 4) + mmc->caps |= MMC_CAP_4_BIT_DATA; + } + + /* Set maximum and minimum frequency */ + if (!mmc->f_max) + of_property_read_u32(node, "spi-max-frequency", &mmc->f_max); + if (!mmc->f_max || mmc->f_max > 52000000) + mmc->f_max = 52000000; + mmc->f_min = 400000; + + /* Sampling register settings, period in picoseconds */ + clock_period = 1000000000000ull / slot->host->sys_freq; + of_property_read_u32(node, "cavium,cmd-clk-skew", &cmd_skew); + of_property_read_u32(node, "cavium,dat-clk-skew", &dat_skew); + slot->cmd_cnt = (cmd_skew + clock_period / 2) / clock_period; + slot->dat_cnt = (dat_skew + clock_period / 2) / clock_period; + + return id; +} + +int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host) +{ + struct cvm_mmc_slot *slot; + struct mmc_host *mmc; + int ret, id; + + mmc = mmc_alloc_host(sizeof(struct cvm_mmc_slot), dev); + if (!mmc) + return -ENOMEM; + + slot = mmc_priv(mmc); + slot->mmc = mmc; + slot->host = host; + + ret = cvm_mmc_of_parse(dev, slot); + if (ret < 0) + goto error; + id = ret; + + /* Set up host parameters */ + mmc->ops = &cvm_mmc_ops; + + mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | + MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD; + + mmc->max_segs = 1; + + /* DMA size field can address up to 8 MB */ + mmc->max_seg_size = 8 * 1024 * 1024; + mmc->max_req_size = mmc->max_seg_size; + /* External DMA is in 512 byte blocks */ + mmc->max_blk_size = 512; + /* DMA block count field is 15 bits */ + mmc->max_blk_count = 32767; + + slot->clock = mmc->f_min; + slot->bus_id = id; + slot->cached_rca = 1; + + host->acquire_bus(host); + host->slot[id] = slot; + cvm_mmc_switch_to(slot); + cvm_mmc_init_lowlevel(slot); + host->release_bus(host); + + ret = mmc_add_host(mmc); + if (ret) { + dev_err(dev, "mmc_add_host() returned %d\n", ret); + slot->host->slot[id] = NULL; + goto error; + } + return 0; + +error: + mmc_free_host(slot->mmc); + return ret; +} + +int cvm_mmc_of_slot_remove(struct cvm_mmc_slot *slot) +{ + mmc_remove_host(slot->mmc); + slot->host->slot[slot->bus_id] = NULL; + mmc_free_host(slot->mmc); + return 0; +} diff --git a/drivers/mmc/host/cavium.h b/drivers/mmc/host/cavium.h new file mode 100644 index 000000000000..f5d2b6156005 --- /dev/null +++ b/drivers/mmc/host/cavium.h @@ -0,0 +1,192 @@ +/* + * Driver for MMC and SSD cards for Cavium OCTEON and ThunderX SOCs. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2012-2017 Cavium Inc. + */ + +#ifndef _CAVIUM_MMC_H_ +#define _CAVIUM_MMC_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CAVIUM_MAX_MMC 4 + +/* DMA register addresses */ +#define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma) + +/* register addresses */ +#define MIO_EMM_CFG(x) (0x00 + x->reg_off) +#define MIO_EMM_SWITCH(x) (0x48 + x->reg_off) +#define MIO_EMM_DMA(x) (0x50 + x->reg_off) +#define MIO_EMM_CMD(x) (0x58 + x->reg_off) +#define MIO_EMM_RSP_STS(x) (0x60 + x->reg_off) +#define MIO_EMM_RSP_LO(x) (0x68 + x->reg_off) +#define MIO_EMM_RSP_HI(x) (0x70 + x->reg_off) +#define MIO_EMM_INT(x) (0x78 + x->reg_off) +#define MIO_EMM_INT_EN(x) (0x80 + x->reg_off) +#define MIO_EMM_WDOG(x) (0x88 + x->reg_off) +#define MIO_EMM_SAMPLE(x) (0x90 + x->reg_off) +#define MIO_EMM_STS_MASK(x) (0x98 + x->reg_off) +#define MIO_EMM_RCA(x) (0xa0 + x->reg_off) +#define MIO_EMM_BUF_IDX(x) (0xe0 + x->reg_off) +#define MIO_EMM_BUF_DAT(x) (0xe8 + x->reg_off) + +struct cvm_mmc_host { + struct device *dev; + void __iomem *base; + void __iomem *dma_base; + int reg_off; + int reg_off_dma; + u64 emm_cfg; + u64 n_minus_one; /* OCTEON II workaround location */ + int last_slot; + struct clk *clk; + int sys_freq; + + struct mmc_request *current_req; + struct sg_mapping_iter smi; + bool dma_active; + + bool has_ciu3; + bool big_dma_addr; + bool need_irq_handler_lock; + spinlock_t irq_handler_lock; + struct semaphore mmc_serializer; + + struct gpio_desc *global_pwr_gpiod; + atomic_t shared_power_users; + + struct cvm_mmc_slot *slot[CAVIUM_MAX_MMC]; + struct platform_device *slot_pdev[CAVIUM_MAX_MMC]; + + void (*set_shared_power)(struct cvm_mmc_host *, int); + void (*acquire_bus)(struct cvm_mmc_host *); + void (*release_bus)(struct cvm_mmc_host *); + void (*int_enable)(struct cvm_mmc_host *, u64); + /* required on some MIPS models */ + void (*dmar_fixup)(struct cvm_mmc_host *, struct mmc_command *, + struct mmc_data *, u64); + void (*dmar_fixup_done)(struct cvm_mmc_host *); +}; + +struct cvm_mmc_slot { + struct mmc_host *mmc; /* slot-level mmc_core object */ + struct cvm_mmc_host *host; /* common hw for all slots */ + + u64 clock; + + u64 cached_switch; + u64 cached_rca; + + unsigned int cmd_cnt; /* sample delay */ + unsigned int dat_cnt; /* sample delay */ + + int bus_id; +}; + +struct cvm_mmc_cr_type { + u8 ctype; + u8 rtype; +}; + +struct cvm_mmc_cr_mods { + u8 ctype_xor; + u8 rtype_xor; +}; + +/* Bitfield definitions */ +#define MIO_EMM_CMD_SKIP_BUSY BIT_ULL(62) +#define MIO_EMM_CMD_BUS_ID GENMASK_ULL(61, 60) +#define MIO_EMM_CMD_VAL BIT_ULL(59) +#define MIO_EMM_CMD_DBUF BIT_ULL(55) +#define MIO_EMM_CMD_OFFSET GENMASK_ULL(54, 49) +#define MIO_EMM_CMD_CTYPE_XOR GENMASK_ULL(42, 41) +#define MIO_EMM_CMD_RTYPE_XOR GENMASK_ULL(40, 38) +#define MIO_EMM_CMD_IDX GENMASK_ULL(37, 32) +#define MIO_EMM_CMD_ARG GENMASK_ULL(31, 0) + +#define MIO_EMM_DMA_SKIP_BUSY BIT_ULL(62) +#define MIO_EMM_DMA_BUS_ID GENMASK_ULL(61, 60) +#define MIO_EMM_DMA_VAL BIT_ULL(59) +#define MIO_EMM_DMA_SECTOR BIT_ULL(58) +#define MIO_EMM_DMA_DAT_NULL BIT_ULL(57) +#define MIO_EMM_DMA_THRES GENMASK_ULL(56, 51) +#define MIO_EMM_DMA_REL_WR BIT_ULL(50) +#define MIO_EMM_DMA_RW BIT_ULL(49) +#define MIO_EMM_DMA_MULTI BIT_ULL(48) +#define MIO_EMM_DMA_BLOCK_CNT GENMASK_ULL(47, 32) +#define MIO_EMM_DMA_CARD_ADDR GENMASK_ULL(31, 0) + +#define MIO_EMM_DMA_CFG_EN BIT_ULL(63) +#define MIO_EMM_DMA_CFG_RW BIT_ULL(62) +#define MIO_EMM_DMA_CFG_CLR BIT_ULL(61) +#define MIO_EMM_DMA_CFG_SWAP32 BIT_ULL(59) +#define MIO_EMM_DMA_CFG_SWAP16 BIT_ULL(58) +#define MIO_EMM_DMA_CFG_SWAP8 BIT_ULL(57) +#define MIO_EMM_DMA_CFG_ENDIAN BIT_ULL(56) +#define MIO_EMM_DMA_CFG_SIZE GENMASK_ULL(55, 36) +#define MIO_EMM_DMA_CFG_ADR GENMASK_ULL(35, 0) + +#define MIO_EMM_INT_SWITCH_ERR BIT_ULL(6) +#define MIO_EMM_INT_SWITCH_DONE BIT_ULL(5) +#define MIO_EMM_INT_DMA_ERR BIT_ULL(4) +#define MIO_EMM_INT_CMD_ERR BIT_ULL(3) +#define MIO_EMM_INT_DMA_DONE BIT_ULL(2) +#define MIO_EMM_INT_CMD_DONE BIT_ULL(1) +#define MIO_EMM_INT_BUF_DONE BIT_ULL(0) + +#define MIO_EMM_RSP_STS_BUS_ID GENMASK_ULL(61, 60) +#define MIO_EMM_RSP_STS_CMD_VAL BIT_ULL(59) +#define MIO_EMM_RSP_STS_SWITCH_VAL BIT_ULL(58) +#define MIO_EMM_RSP_STS_DMA_VAL BIT_ULL(57) +#define MIO_EMM_RSP_STS_DMA_PEND BIT_ULL(56) +#define MIO_EMM_RSP_STS_DBUF_ERR BIT_ULL(28) +#define MIO_EMM_RSP_STS_DBUF BIT_ULL(23) +#define MIO_EMM_RSP_STS_BLK_TIMEOUT BIT_ULL(22) +#define MIO_EMM_RSP_STS_BLK_CRC_ERR BIT_ULL(21) +#define MIO_EMM_RSP_STS_RSP_BUSYBIT BIT_ULL(20) +#define MIO_EMM_RSP_STS_STP_TIMEOUT BIT_ULL(19) +#define MIO_EMM_RSP_STS_STP_CRC_ERR BIT_ULL(18) +#define MIO_EMM_RSP_STS_STP_BAD_STS BIT_ULL(17) +#define MIO_EMM_RSP_STS_STP_VAL BIT_ULL(16) +#define MIO_EMM_RSP_STS_RSP_TIMEOUT BIT_ULL(15) +#define MIO_EMM_RSP_STS_RSP_CRC_ERR BIT_ULL(14) +#define MIO_EMM_RSP_STS_RSP_BAD_STS BIT_ULL(13) +#define MIO_EMM_RSP_STS_RSP_VAL BIT_ULL(12) +#define MIO_EMM_RSP_STS_RSP_TYPE GENMASK_ULL(11, 9) +#define MIO_EMM_RSP_STS_CMD_TYPE GENMASK_ULL(8, 7) +#define MIO_EMM_RSP_STS_CMD_IDX GENMASK_ULL(6, 1) +#define MIO_EMM_RSP_STS_CMD_DONE BIT_ULL(0) + +#define MIO_EMM_SAMPLE_CMD_CNT GENMASK_ULL(25, 16) +#define MIO_EMM_SAMPLE_DAT_CNT GENMASK_ULL(9, 0) + +#define MIO_EMM_SWITCH_BUS_ID GENMASK_ULL(61, 60) +#define MIO_EMM_SWITCH_EXE BIT_ULL(59) +#define MIO_EMM_SWITCH_ERR0 BIT_ULL(58) +#define MIO_EMM_SWITCH_ERR1 BIT_ULL(57) +#define MIO_EMM_SWITCH_ERR2 BIT_ULL(56) +#define MIO_EMM_SWITCH_HS_TIMING BIT_ULL(48) +#define MIO_EMM_SWITCH_BUS_WIDTH GENMASK_ULL(42, 40) +#define MIO_EMM_SWITCH_POWER_CLASS GENMASK_ULL(35, 32) +#define MIO_EMM_SWITCH_CLK_HI GENMASK_ULL(31, 16) +#define MIO_EMM_SWITCH_CLK_LO GENMASK_ULL(15, 0) + +/* Protoypes */ +irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id); +int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host); +int cvm_mmc_of_slot_remove(struct cvm_mmc_slot *slot); +extern const char *cvm_mmc_irq_names[]; + +#endif From 166bac38c3c569508e695aca99fdc2def2ae2198 Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 30 Mar 2017 17:31:25 +0200 Subject: [PATCH 107/148] mmc: cavium: Add MMC PCI driver for ThunderX SOCs Add a platform driver for ThunderX ARM SOCs. Signed-off-by: Jan Glauber Signed-off-by: Ulf Hansson --- drivers/mmc/host/Kconfig | 11 ++ drivers/mmc/host/Makefile | 2 + drivers/mmc/host/cavium-thunderx.c | 195 +++++++++++++++++++++++++++++ drivers/mmc/host/cavium.h | 7 ++ 4 files changed, 215 insertions(+) create mode 100644 drivers/mmc/host/cavium-thunderx.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index a638cd0d80be..7736a93386f7 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -622,6 +622,17 @@ config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND help If you say yes here SD-Cards may work on the EZkit. +config MMC_CAVIUM_THUNDERX + tristate "Cavium ThunderX SD/MMC Card Interface support" + depends on PCI && 64BIT && (ARM64 || COMPILE_TEST) + depends on GPIOLIB + depends on OF_ADDRESS + help + This selects Cavium ThunderX SD/MMC Card Interface. + If you have an Cavium ARM64 board with a Multimedia Card slot + or builtin eMMC chip say Y or M here. If built as a module + the module will be called thunderx_mmc.ko. + config MMC_DW tristate "Synopsys DesignWare Memory Card Interface" depends on HAS_DMA diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index bc2c2e2c68c0..ec78296ef053 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -42,6 +42,8 @@ obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o +thunderx-mmc-objs := cavium.o cavium-thunderx.o +obj-$(CONFIG_MMC_CAVIUM_THUNDERX) += thunderx-mmc.o obj-$(CONFIG_MMC_DW) += dw_mmc.o obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o diff --git a/drivers/mmc/host/cavium-thunderx.c b/drivers/mmc/host/cavium-thunderx.c new file mode 100644 index 000000000000..cba108b72bac --- /dev/null +++ b/drivers/mmc/host/cavium-thunderx.c @@ -0,0 +1,195 @@ +/* + * Driver for MMC and SSD cards for Cavium ThunderX SOCs. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2016 Cavium Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include "cavium.h" + +static void thunder_mmc_acquire_bus(struct cvm_mmc_host *host) +{ + down(&host->mmc_serializer); +} + +static void thunder_mmc_release_bus(struct cvm_mmc_host *host) +{ + up(&host->mmc_serializer); +} + +static void thunder_mmc_int_enable(struct cvm_mmc_host *host, u64 val) +{ + writeq(val, host->base + MIO_EMM_INT(host)); + writeq(val, host->base + MIO_EMM_INT_EN_SET(host)); +} + +static int thunder_mmc_register_interrupts(struct cvm_mmc_host *host, + struct pci_dev *pdev) +{ + int nvec, ret, i; + + nvec = pci_alloc_irq_vectors(pdev, 1, 9, PCI_IRQ_MSIX); + if (nvec < 0) + return nvec; + + /* register interrupts */ + for (i = 0; i < nvec; i++) { + ret = devm_request_irq(&pdev->dev, pci_irq_vector(pdev, i), + cvm_mmc_interrupt, + 0, cvm_mmc_irq_names[i], host); + if (ret) + return ret; + } + return 0; +} + +static int thunder_mmc_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct device_node *child_node; + struct cvm_mmc_host *host; + int ret, i = 0; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + pci_set_drvdata(pdev, host); + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pci_request_regions(pdev, KBUILD_MODNAME); + if (ret) + return ret; + + host->base = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); + if (!host->base) + return -EINVAL; + + /* On ThunderX these are identical */ + host->dma_base = host->base; + + host->reg_off = 0x2000; + host->reg_off_dma = 0x180; + + host->clk = devm_clk_get(dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + ret = clk_prepare_enable(host->clk); + if (ret) + return ret; + host->sys_freq = clk_get_rate(host->clk); + + spin_lock_init(&host->irq_handler_lock); + sema_init(&host->mmc_serializer, 1); + + host->dev = dev; + host->acquire_bus = thunder_mmc_acquire_bus; + host->release_bus = thunder_mmc_release_bus; + host->int_enable = thunder_mmc_int_enable; + + host->big_dma_addr = true; + host->need_irq_handler_lock = true; + host->last_slot = -1; + + ret = dma_set_mask(dev, DMA_BIT_MASK(48)); + if (ret) + goto error; + + /* + * Clear out any pending interrupts that may be left over from + * bootloader. Writing 1 to the bits clears them. + */ + writeq(127, host->base + MIO_EMM_INT_EN(host)); + writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C(host)); + + ret = thunder_mmc_register_interrupts(host, pdev); + if (ret) + goto error; + + for_each_child_of_node(node, child_node) { + /* + * mmc_of_parse and devm* require one device per slot. + * Create a dummy device per slot and set the node pointer to + * the slot. The easiest way to get this is using + * of_platform_device_create. + */ + if (of_device_is_compatible(child_node, "mmc-slot")) { + host->slot_pdev[i] = of_platform_device_create(child_node, NULL, + &pdev->dev); + if (!host->slot_pdev[i]) + continue; + + ret = cvm_mmc_of_slot_probe(&host->slot_pdev[i]->dev, host); + if (ret) + goto error; + } + i++; + } + dev_info(dev, "probed\n"); + return 0; + +error: + clk_disable_unprepare(host->clk); + return ret; +} + +static void thunder_mmc_remove(struct pci_dev *pdev) +{ + struct cvm_mmc_host *host = pci_get_drvdata(pdev); + u64 dma_cfg; + int i; + + for (i = 0; i < CAVIUM_MAX_MMC; i++) + if (host->slot[i]) + cvm_mmc_of_slot_remove(host->slot[i]); + + dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); + dma_cfg &= ~MIO_EMM_DMA_CFG_EN; + writeq(dma_cfg, host->dma_base + MIO_EMM_DMA_CFG(host)); + + clk_disable_unprepare(host->clk); +} + +static const struct pci_device_id thunder_mmc_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa010) }, + { 0, } /* end of table */ +}; + +static struct pci_driver thunder_mmc_driver = { + .name = KBUILD_MODNAME, + .id_table = thunder_mmc_id_table, + .probe = thunder_mmc_probe, + .remove = thunder_mmc_remove, +}; + +static int __init thunder_mmc_init_module(void) +{ + return pci_register_driver(&thunder_mmc_driver); +} + +static void __exit thunder_mmc_exit_module(void) +{ + pci_unregister_driver(&thunder_mmc_driver); +} + +module_init(thunder_mmc_init_module); +module_exit(thunder_mmc_exit_module); + +MODULE_AUTHOR("Cavium Inc."); +MODULE_DESCRIPTION("Cavium ThunderX eMMC Driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, thunder_mmc_id_table); diff --git a/drivers/mmc/host/cavium.h b/drivers/mmc/host/cavium.h index f5d2b6156005..66eec210b945 100644 --- a/drivers/mmc/host/cavium.h +++ b/drivers/mmc/host/cavium.h @@ -24,6 +24,11 @@ /* DMA register addresses */ #define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma) +#define MIO_EMM_DMA_ADR(x) (0x08 + x->reg_off_dma) +#define MIO_EMM_DMA_INT(x) (0x10 + x->reg_off_dma) +#define MIO_EMM_DMA_INT_W1S(x) (0x18 + x->reg_off_dma) +#define MIO_EMM_DMA_INT_ENA_W1S(x) (0x20 + x->reg_off_dma) +#define MIO_EMM_DMA_INT_ENA_W1C(x) (0x28 + x->reg_off_dma) /* register addresses */ #define MIO_EMM_CFG(x) (0x00 + x->reg_off) @@ -39,6 +44,8 @@ #define MIO_EMM_SAMPLE(x) (0x90 + x->reg_off) #define MIO_EMM_STS_MASK(x) (0x98 + x->reg_off) #define MIO_EMM_RCA(x) (0xa0 + x->reg_off) +#define MIO_EMM_INT_EN_SET(x) (0xb0 + x->reg_off) +#define MIO_EMM_INT_EN_CLR(x) (0xb8 + x->reg_off) #define MIO_EMM_BUF_IDX(x) (0xe0 + x->reg_off) #define MIO_EMM_BUF_DAT(x) (0xe8 + x->reg_off) From cd76e5c565e82af62f708120068ec5d226d98c95 Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 30 Mar 2017 17:31:26 +0200 Subject: [PATCH 108/148] mmc: cavium: Add scatter-gather DMA support Add Support for the scatter-gather DMA available in the ThunderX MMC units. Up to 16 DMA requests can be processed together. Signed-off-by: Jan Glauber Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium-thunderx.c | 5 +- drivers/mmc/host/cavium.c | 104 ++++++++++++++++++++++++++++- drivers/mmc/host/cavium.h | 28 ++++++-- 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/host/cavium-thunderx.c b/drivers/mmc/host/cavium-thunderx.c index cba108b72bac..65244e8df5dd 100644 --- a/drivers/mmc/host/cavium-thunderx.c +++ b/drivers/mmc/host/cavium-thunderx.c @@ -82,7 +82,7 @@ static int thunder_mmc_probe(struct pci_dev *pdev, host->dma_base = host->base; host->reg_off = 0x2000; - host->reg_off_dma = 0x180; + host->reg_off_dma = 0x160; host->clk = devm_clk_get(dev, NULL); if (IS_ERR(host->clk)) @@ -101,6 +101,7 @@ static int thunder_mmc_probe(struct pci_dev *pdev, host->release_bus = thunder_mmc_release_bus; host->int_enable = thunder_mmc_int_enable; + host->use_sg = true; host->big_dma_addr = true; host->need_irq_handler_lock = true; host->last_slot = -1; @@ -115,6 +116,8 @@ static int thunder_mmc_probe(struct pci_dev *pdev, */ writeq(127, host->base + MIO_EMM_INT_EN(host)); writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C(host)); + /* Clear DMA FIFO */ + writeq(BIT_ULL(16), host->base + MIO_EMM_DMA_FIFO_CFG(host)); ret = thunder_mmc_register_interrupts(host, pdev); if (ret) diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c index 910e290579bf..eebb387dc8c4 100644 --- a/drivers/mmc/host/cavium.c +++ b/drivers/mmc/host/cavium.c @@ -377,9 +377,32 @@ static int finish_dma_single(struct cvm_mmc_host *host, struct mmc_data *data) return 1; } +static int finish_dma_sg(struct cvm_mmc_host *host, struct mmc_data *data) +{ + u64 fifo_cfg; + int count; + + /* Check if there are any pending requests left */ + fifo_cfg = readq(host->dma_base + MIO_EMM_DMA_FIFO_CFG(host)); + count = FIELD_GET(MIO_EMM_DMA_FIFO_CFG_COUNT, fifo_cfg); + if (count) + dev_err(host->dev, "%u requests still pending\n", count); + + data->bytes_xfered = data->blocks * data->blksz; + data->error = 0; + + /* Clear and disable FIFO */ + writeq(BIT_ULL(16), host->dma_base + MIO_EMM_DMA_FIFO_CFG(host)); + dma_unmap_sg(host->dev, data->sg, data->sg_len, get_dma_dir(data)); + return 1; +} + static int finish_dma(struct cvm_mmc_host *host, struct mmc_data *data) { - return finish_dma_single(host, data); + if (host->use_sg && data->sg_len > 1) + return finish_dma_sg(host, data); + else + return finish_dma_single(host, data); } static int check_status(u64 rsp_sts) @@ -522,9 +545,81 @@ static u64 prepare_dma_single(struct cvm_mmc_host *host, struct mmc_data *data) return addr; } +/* + * Queue complete sg list into the FIFO. + * Returns 0 on error, 1 otherwise. + */ +static u64 prepare_dma_sg(struct cvm_mmc_host *host, struct mmc_data *data) +{ + struct scatterlist *sg; + u64 fifo_cmd, addr; + int count, i, rw; + + count = dma_map_sg(host->dev, data->sg, data->sg_len, + get_dma_dir(data)); + if (!count) + return 0; + if (count > 16) + goto error; + + /* Enable FIFO by removing CLR bit */ + writeq(0, host->dma_base + MIO_EMM_DMA_FIFO_CFG(host)); + + for_each_sg(data->sg, sg, count, i) { + /* Program DMA address */ + addr = sg_dma_address(sg); + if (addr & 7) + goto error; + writeq(addr, host->dma_base + MIO_EMM_DMA_FIFO_ADR(host)); + + /* + * If we have scatter-gather support we also have an extra + * register for the DMA addr, so no need to check + * host->big_dma_addr here. + */ + rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0; + fifo_cmd = FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_RW, rw); + + /* enable interrupts on the last element */ + fifo_cmd |= FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_INTDIS, + (i + 1 == count) ? 0 : 1); + +#ifdef __LITTLE_ENDIAN + fifo_cmd |= FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_ENDIAN, 1); +#endif + fifo_cmd |= FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_SIZE, + sg_dma_len(sg) / 8 - 1); + /* + * The write copies the address and the command to the FIFO + * and increments the FIFO's COUNT field. + */ + writeq(fifo_cmd, host->dma_base + MIO_EMM_DMA_FIFO_CMD(host)); + pr_debug("[%s] sg_dma_len: %u sg_elem: %d/%d\n", + (rw) ? "W" : "R", sg_dma_len(sg), i, count); + } + + /* + * In difference to prepare_dma_single we don't return the + * address here, as it would not make sense for scatter-gather. + * The dma fixup is only required on models that don't support + * scatter-gather, so that is not a problem. + */ + return 1; + +error: + WARN_ON_ONCE(1); + dma_unmap_sg(host->dev, data->sg, data->sg_len, get_dma_dir(data)); + /* Disable FIFO */ + writeq(BIT_ULL(16), host->dma_base + MIO_EMM_DMA_FIFO_CFG(host)); + return 0; +} + static u64 prepare_dma(struct cvm_mmc_host *host, struct mmc_data *data) { - return prepare_dma_single(host, data); + if (host->use_sg && data->sg_len > 1) + return prepare_dma_sg(host, data); + else + return prepare_dma_single(host, data); } static u64 prepare_ext_dma(struct mmc_host *mmc, struct mmc_request *mrq) @@ -940,7 +1035,10 @@ int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host) mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD; - mmc->max_segs = 1; + if (host->use_sg) + mmc->max_segs = 16; + else + mmc->max_segs = 1; /* DMA size field can address up to 8 MB */ mmc->max_seg_size = 8 * 1024 * 1024; diff --git a/drivers/mmc/host/cavium.h b/drivers/mmc/host/cavium.h index 66eec210b945..f3eea5eaa678 100644 --- a/drivers/mmc/host/cavium.h +++ b/drivers/mmc/host/cavium.h @@ -23,12 +23,15 @@ #define CAVIUM_MAX_MMC 4 /* DMA register addresses */ -#define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma) -#define MIO_EMM_DMA_ADR(x) (0x08 + x->reg_off_dma) -#define MIO_EMM_DMA_INT(x) (0x10 + x->reg_off_dma) -#define MIO_EMM_DMA_INT_W1S(x) (0x18 + x->reg_off_dma) -#define MIO_EMM_DMA_INT_ENA_W1S(x) (0x20 + x->reg_off_dma) -#define MIO_EMM_DMA_INT_ENA_W1C(x) (0x28 + x->reg_off_dma) +#define MIO_EMM_DMA_FIFO_CFG(x) (0x00 + x->reg_off_dma) +#define MIO_EMM_DMA_FIFO_ADR(x) (0x10 + x->reg_off_dma) +#define MIO_EMM_DMA_FIFO_CMD(x) (0x18 + x->reg_off_dma) +#define MIO_EMM_DMA_CFG(x) (0x20 + x->reg_off_dma) +#define MIO_EMM_DMA_ADR(x) (0x28 + x->reg_off_dma) +#define MIO_EMM_DMA_INT(x) (0x30 + x->reg_off_dma) +#define MIO_EMM_DMA_INT_W1S(x) (0x38 + x->reg_off_dma) +#define MIO_EMM_DMA_INT_ENA_W1S(x) (0x40 + x->reg_off_dma) +#define MIO_EMM_DMA_INT_ENA_W1C(x) (0x48 + x->reg_off_dma) /* register addresses */ #define MIO_EMM_CFG(x) (0x00 + x->reg_off) @@ -64,6 +67,7 @@ struct cvm_mmc_host { struct mmc_request *current_req; struct sg_mapping_iter smi; bool dma_active; + bool use_sg; bool has_ciu3; bool big_dma_addr; @@ -113,6 +117,18 @@ struct cvm_mmc_cr_mods { }; /* Bitfield definitions */ +#define MIO_EMM_DMA_FIFO_CFG_CLR BIT_ULL(16) +#define MIO_EMM_DMA_FIFO_CFG_INT_LVL GENMASK_ULL(12, 8) +#define MIO_EMM_DMA_FIFO_CFG_COUNT GENMASK_ULL(4, 0) + +#define MIO_EMM_DMA_FIFO_CMD_RW BIT_ULL(62) +#define MIO_EMM_DMA_FIFO_CMD_INTDIS BIT_ULL(60) +#define MIO_EMM_DMA_FIFO_CMD_SWAP32 BIT_ULL(59) +#define MIO_EMM_DMA_FIFO_CMD_SWAP16 BIT_ULL(58) +#define MIO_EMM_DMA_FIFO_CMD_SWAP8 BIT_ULL(57) +#define MIO_EMM_DMA_FIFO_CMD_ENDIAN BIT_ULL(56) +#define MIO_EMM_DMA_FIFO_CMD_SIZE GENMASK_ULL(55, 36) + #define MIO_EMM_CMD_SKIP_BUSY BIT_ULL(62) #define MIO_EMM_CMD_BUS_ID GENMASK_ULL(61, 60) #define MIO_EMM_CMD_VAL BIT_ULL(59) From 4ce944074e5e2cb7ee48ff36608d65d0bc9c7647 Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 30 Mar 2017 17:31:27 +0200 Subject: [PATCH 109/148] mmc: cavium: Support DDR mode for eMMC devices Add support for switching to DDR mode for eMMC devices. Signed-off-by: Jan Glauber Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c index eebb387dc8c4..d842b6986189 100644 --- a/drivers/mmc/host/cavium.c +++ b/drivers/mmc/host/cavium.c @@ -864,6 +864,10 @@ static void cvm_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) break; } + /* DDR is available for 4/8 bit bus width */ + if (ios->bus_width && ios->timing == MMC_TIMING_MMC_DDR52) + bus_width |= 4; + /* Change the clock frequency. */ clock = ios->clock; if (clock > 52000000) @@ -1032,8 +1036,14 @@ int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host) /* Set up host parameters */ mmc->ops = &cvm_mmc_ops; + /* + * We only have a 3.3v supply, we cannot support any + * of the UHS modes. We do support the high speed DDR + * modes up to 52MHz. + */ mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | - MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD; + MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD | + MMC_CAP_3_3V_DDR; if (host->use_sg) mmc->max_segs = 16; From 25fc846503738c3d4ca886cfee4b4ff4415d0cec Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 30 Mar 2017 17:31:28 +0200 Subject: [PATCH 110/148] MAINTAINERS: Add entry for Cavium MMC driver Signed-off-by: Jan Glauber Signed-off-by: David Daney Signed-off-by: Steven J. Hill Signed-off-by: Ulf Hansson --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 38d3e4ed7208..2816dd9114cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3055,6 +3055,14 @@ S: Supported F: drivers/i2c/busses/i2c-octeon* F: drivers/i2c/busses/i2c-thunderx* +CAVIUM MMC DRIVER +M: Jan Glauber +M: David Daney +M: Steven J. Hill +W: http://www.cavium.com +S: Supported +F: drivers/mmc/host/cavium* + CAVIUM LIQUIDIO NETWORK DRIVER M: Derek Chickles M: Satanand Burla From a8309cedcdce94fc6e36d49d0bf8e75d5813ebc6 Mon Sep 17 00:00:00 2001 From: Konstantin Porotchkin Date: Thu, 30 Mar 2017 17:22:53 +0200 Subject: [PATCH 111/148] clk: apn806: Add eMMC clock to system controller driver Add fixed clock of 400MHz to system controller driver. This clock is used as SD/eMMC clock source. Signed-off-by: Konstantin Porotchkin Reviewed-by: Omri Itach Reviewed-by: Hanna Hawa [fixed up conflicts, added error handling --rmk] Signed-off-by: Russell King Acked-by: Stephen Boyd Signed-off-by: Gregory CLEMENT Signed-off-by: Ulf Hansson --- arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 3 ++- drivers/clk/mvebu/ap806-system-controller.c | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi index a749ba2edec4..5019c8f4acd0 100644 --- a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi +++ b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi @@ -235,7 +235,8 @@ #clock-cells = <1>; clock-output-names = "ap-cpu-cluster-0", "ap-cpu-cluster-1", - "ap-fixed", "ap-mss"; + "ap-fixed", "ap-mss", + "ap-emmc"; reg = <0x6f4000 0x1000>; }; }; diff --git a/drivers/clk/mvebu/ap806-system-controller.c b/drivers/clk/mvebu/ap806-system-controller.c index f17702107ac5..901d89c4ab4a 100644 --- a/drivers/clk/mvebu/ap806-system-controller.c +++ b/drivers/clk/mvebu/ap806-system-controller.c @@ -23,7 +23,7 @@ #define AP806_SAR_REG 0x400 #define AP806_SAR_CLKFREQ_MODE_MASK 0x1f -#define AP806_CLK_NUM 4 +#define AP806_CLK_NUM 5 static struct clk *ap806_clks[AP806_CLK_NUM]; @@ -135,6 +135,17 @@ static int ap806_syscon_clk_probe(struct platform_device *pdev) goto fail3; } + /* eMMC Clock is fixed clock divided by 3 */ + of_property_read_string_index(np, "clock-output-names", + 4, &name); + ap806_clks[4] = clk_register_fixed_factor(NULL, name, fixedclk_name, + 0, 1, 3); + if (IS_ERR(ap806_clks[4])) { + ret = PTR_ERR(ap806_clks[4]); + goto fail4; + } + + of_clk_add_provider(np, of_clk_src_onecell_get, &ap806_clk_data); ret = of_clk_add_provider(np, of_clk_src_onecell_get, &ap806_clk_data); if (ret) goto fail_clk_add; @@ -142,6 +153,8 @@ static int ap806_syscon_clk_probe(struct platform_device *pdev) return 0; fail_clk_add: + clk_unregister_fixed_factor(ap806_clks[4]); +fail4: clk_unregister_fixed_factor(ap806_clks[3]); fail3: clk_unregister_fixed_rate(ap806_clks[2]); From b92a3bcc7c3ca4ee8d228f033de32aefff3c74a4 Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Thu, 30 Mar 2017 17:22:54 +0200 Subject: [PATCH 112/148] clk: apn806: Turn the eMMC clock as optional for dts backwards compatible First version of the binding didn't have the eMMC clock. This patch allows to not registering the eMMC clock if it is not present in the device tree. Then the device tree can be backwards compatible. Suggested-by: Stephen Boyd Signed-off-by: Gregory CLEMENT Signed-off-by: Ulf Hansson --- drivers/clk/mvebu/ap806-system-controller.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/clk/mvebu/ap806-system-controller.c b/drivers/clk/mvebu/ap806-system-controller.c index 901d89c4ab4a..103fe18a3c29 100644 --- a/drivers/clk/mvebu/ap806-system-controller.c +++ b/drivers/clk/mvebu/ap806-system-controller.c @@ -136,13 +136,19 @@ static int ap806_syscon_clk_probe(struct platform_device *pdev) } /* eMMC Clock is fixed clock divided by 3 */ - of_property_read_string_index(np, "clock-output-names", - 4, &name); - ap806_clks[4] = clk_register_fixed_factor(NULL, name, fixedclk_name, - 0, 1, 3); - if (IS_ERR(ap806_clks[4])) { - ret = PTR_ERR(ap806_clks[4]); - goto fail4; + if (of_property_read_string_index(np, "clock-output-names", + 4, &name)) { + ap806_clk_data.clk_num--; + dev_warn(&pdev->dev, + "eMMC clock mising: update the device tree!\n"); + } else { + ap806_clks[4] = clk_register_fixed_factor(NULL, name, + fixedclk_name, + 0, 1, 3); + if (IS_ERR(ap806_clks[4])) { + ret = PTR_ERR(ap806_clks[4]); + goto fail4; + } } of_clk_add_provider(np, of_clk_src_onecell_get, &ap806_clk_data); From 6a6d4ceb7be0c72b2190f4b6b9d5d8982957fc04 Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:22:55 +0200 Subject: [PATCH 113/148] mmc: sdhci: Export sdhci_set_ios() from sdhci.c Export sdhci_set_ios() in sdhci.c. Thus vendor sdhci driver can implement its own set_ios() routine. Signed-off-by: Hu Ziji Signed-off-by: Gregory CLEMENT Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci.c | 3 ++- drivers/mmc/host/sdhci.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a1a38e04a9a5..797babc597c1 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1583,7 +1583,7 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing) } EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling); -static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct sdhci_host *host = mmc_priv(mmc); u8 ctrl; @@ -1738,6 +1738,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) mmiowb(); } +EXPORT_SYMBOL_GPL(sdhci_set_ios); static int sdhci_get_cd(struct mmc_host *mmc) { diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index fdb5d7e1911f..b0cb805937c0 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -703,6 +703,7 @@ 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_set_ios(struct mmc_host *mmc, struct mmc_ios *ios); #ifdef CONFIG_PM int sdhci_suspend_host(struct sdhci_host *host); From c376ea9e46215fbfa09b371f79c821fa5a51f26e Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:22:56 +0200 Subject: [PATCH 114/148] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c Export sdhci_start_signal_voltage_switch() from sdhci.c. Thus vendor sdhci driver can implement its own signal voltage switch routine. Signed-off-by: Hu Ziji Signed-off-by: Gregory CLEMENT Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci.c | 5 +++-- drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 797babc597c1..4b8ed3bd57f0 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1853,8 +1853,8 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) pm_runtime_put_noidle(host->mmc->parent); } -static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, - struct mmc_ios *ios) +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) { struct sdhci_host *host = mmc_priv(mmc); u16 ctrl; @@ -1946,6 +1946,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, return 0; } } +EXPORT_SYMBOL_GPL(sdhci_start_signal_voltage_switch); static int sdhci_card_busy(struct mmc_host *mmc) { diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index b0cb805937c0..a741d8da6227 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -704,6 +704,8 @@ 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_set_ios(struct mmc_host *mmc, struct mmc_ios *ios); +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios); #ifdef CONFIG_PM int sdhci_suspend_host(struct sdhci_host *host); From 2f05b6ab5fa7f279015c70f625ec5c532781eb2b Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:22:57 +0200 Subject: [PATCH 115/148] mmc: sdhci: Export sdhci_enable_sdio_irq() from sdhci.c Export sdhci_enable_sdio_irq() from sdhci.c. Thus vendor SDHC driver can implement its specific SDIO irq control. Signed-off-by: Hu Ziji Signed-off-by: Gregory CLEMENT Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci.c | 3 ++- drivers/mmc/host/sdhci.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 4b8ed3bd57f0..4bdad8959ec2 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1832,7 +1832,7 @@ static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable) } } -static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) +void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) { struct sdhci_host *host = mmc_priv(mmc); unsigned long flags; @@ -1852,6 +1852,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) if (!enable) pm_runtime_put_noidle(host->mmc->parent); } +EXPORT_SYMBOL_GPL(sdhci_enable_sdio_irq); int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, struct mmc_ios *ios) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index a741d8da6227..78437f82609e 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -706,6 +706,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode); 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); #ifdef CONFIG_PM int sdhci_suspend_host(struct sdhci_host *host); From 66c659cf5cda6f21c0f7832b735c180a9f99a790 Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:22:58 +0200 Subject: [PATCH 116/148] dt: bindings: Add bindings for Marvell Xenon SD Host Controller Marvell Xenon SDHC can support eMMC/SD/SDIO. Add Xenon-specific properties. Also add properties for Xenon PHY setting. Signed-off-by: Hu Ziji Signed-off-by: Gregory CLEMENT Acked-by: Rob Herring Signed-off-by: Ulf Hansson --- .../bindings/mmc/marvell,xenon-sdhci.txt | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt new file mode 100644 index 000000000000..b878a1e305af --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt @@ -0,0 +1,170 @@ +Marvell Xenon SDHCI Controller device tree bindings +This file documents differences between the core mmc properties +described by mmc.txt and the properties used by the Xenon implementation. + +Multiple SDHCs might be put into a single Xenon IP, to save size and cost. +Each SDHC is independent and owns independent resources, such as register sets, +clock and PHY. +Each SDHC should have an independent device tree node. + +Required Properties: +- compatible: should be one of the following + - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SoC. + Must provide a second register area and marvell,pad-type. + - "marvell,armada-ap806-sdhci": For controllers on Armada AP806. + - "marvell,armada-cp110-sdhci": For controllers on Armada CP110. + +- clocks: + Array of clocks required for SDHC. + Require at least input clock for Xenon IP core. + +- clock-names: + Array of names corresponding to clocks property. + The input clock for Xenon IP core should be named as "core". + +- reg: + * For "marvell,armada-3700-sdhci", two register areas. + The first one for Xenon IP register. The second one for the Armada 3700 SoC + PHY PAD Voltage Control register. + Please follow the examples with compatible "marvell,armada-3700-sdhci" + in below. + Please also check property marvell,pad-type in below. + + * For other compatible strings, one register area for Xenon IP. + +Optional Properties: +- marvell,xenon-sdhc-id: + Indicate the corresponding bit index of current SDHC in + SDHC System Operation Control Register Bit[7:0]. + Set/clear the corresponding bit to enable/disable current SDHC. + If Xenon IP contains only one SDHC, this property is optional. + +- marvell,xenon-phy-type: + Xenon support multiple types of PHYs. + To select eMMC 5.1 PHY, set: + marvell,xenon-phy-type = "emmc 5.1 phy" + eMMC 5.1 PHY is the default choice if this property is not provided. + To select eMMC 5.0 PHY, set: + marvell,xenon-phy-type = "emmc 5.0 phy" + + All those types of PHYs can support eMMC, SD and SDIO. + Please note that this property only presents the type of PHY. + It doesn't stand for the entire SDHC type or property. + For example, "emmc 5.1 phy" doesn't mean that this Xenon SDHC only + supports eMMC 5.1. + +- marvell,xenon-phy-znr: + Set PHY ZNR value. + Only available for eMMC PHY. + Valid range = [0:0x1F]. + ZNR is set as 0xF by default if this property is not provided. + +- marvell,xenon-phy-zpr: + Set PHY ZPR value. + Only available for eMMC PHY. + Valid range = [0:0x1F]. + ZPR is set as 0xF by default if this property is not provided. + +- marvell,xenon-phy-nr-success-tun: + Set the number of required consecutive successful sampling points + used to identify a valid sampling window, in tuning process. + Valid range = [1:7]. + Set as 0x4 by default if this property is not provided. + +- marvell,xenon-phy-tun-step-divider: + Set the divider for calculating TUN_STEP. + Set as 64 by default if this property is not provided. + +- marvell,xenon-phy-slow-mode: + If this property is selected, transfers will bypass PHY. + Only available when bus frequency lower than 55MHz in SDR mode. + Disabled by default. Please only try this property if timing issues + always occur with PHY enabled in eMMC HS SDR, SD SDR12, SD SDR25, + SD Default Speed and HS mode and eMMC legacy speed mode. + +- marvell,xenon-tun-count: + Xenon SDHC SoC usually doesn't provide re-tuning counter in + Capabilities Register 3 Bit[11:8]. + This property provides the re-tuning counter. + If this property is not set, default re-tuning counter will + be set as 0x9 in driver. + +- marvell,pad-type: + Type of Armada 3700 SoC PHY PAD Voltage Controller register. + Only valid when "marvell,armada-3700-sdhci" is selected. + Two types: "sd" and "fixed-1-8v". + If "sd" is selected, SoC PHY PAD is set as 3.3V at the beginning and is + switched to 1.8V when later in higher speed mode. + If "fixed-1-8v" is selected, SoC PHY PAD is fixed 1.8V, such as for eMMC. + Please follow the examples with compatible "marvell,armada-3700-sdhci" + in below. + +Example: +- For eMMC: + + sdhci@aa0000 { + compatible = "marvell,armada-ap806-sdhci"; + reg = <0xaa0000 0x1000>; + interrupts = + clocks = <&emmc_clk>; + clock-names = "core"; + bus-width = <4>; + marvell,xenon-phy-slow-mode; + marvell,xenon-tun-count = <11>; + non-removable; + no-sd; + no-sdio; + + /* Vmmc and Vqmmc are both fixed */ + }; + +- For SD/SDIO: + + sdhci@ab0000 { + compatible = "marvell,armada-cp110-sdhci"; + reg = <0xab0000 0x1000>; + interrupts = + vqmmc-supply = <&sd_vqmmc_regulator>; + vmmc-supply = <&sd_vmmc_regulator>; + clocks = <&sdclk>; + clock-names = "core"; + bus-width = <4>; + marvell,xenon-tun-count = <9>; + }; + +- For eMMC with compatible "marvell,armada-3700-sdhci": + + sdhci@aa0000 { + compatible = "marvell,armada-3700-sdhci"; + reg = <0xaa0000 0x1000>, + ; + interrupts = + clocks = <&emmcclk>; + clock-names = "core"; + bus-width = <8>; + mmc-ddr-1_8v; + mmc-hs400-1_8v; + non-removable; + no-sd; + no-sdio; + + /* Vmmc and Vqmmc are both fixed */ + + marvell,pad-type = "fixed-1-8v"; + }; + +- For SD/SDIO with compatible "marvell,armada-3700-sdhci": + + sdhci@ab0000 { + compatible = "marvell,armada-3700-sdhci"; + reg = <0xab0000 0x1000>, + ; + interrupts = + vqmmc-supply = <&sd_regulator>; + /* Vmmc is fixed */ + clocks = <&sdclk>; + clock-names = "core"; + bus-width = <4>; + + marvell,pad-type = "sd"; + }; From 3a3748dba881ed23ba8465dd6d66a267af89cdc0 Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:22:59 +0200 Subject: [PATCH 117/148] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality Add Xenon eMMC/SD/SDIO host controller core functionality. Add Xenon specific initialization process. Add Xenon specific mmc_host_ops APIs. Add Xenon specific register definitions. Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig. Marvell Xenon SDHC conforms to SD Physical Layer Specification Version 3.01 and is designed according to the guidelines provided in the SD Host Controller Standard Specification Version 3.00. Signed-off-by: Hu Ziji Signed-off-by: Gregory CLEMENT Signed-off-by: Ulf Hansson --- drivers/mmc/host/Kconfig | 8 + drivers/mmc/host/Makefile | 3 + drivers/mmc/host/sdhci-xenon.c | 541 +++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci-xenon.h | 60 ++++ 4 files changed, 612 insertions(+) create mode 100644 drivers/mmc/host/sdhci-xenon.c create mode 100644 drivers/mmc/host/sdhci-xenon.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 7736a93386f7..9cee339f337c 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -853,3 +853,11 @@ config MMC_SDHCI_BRCMSTB Broadcom STB SoCs. If unsure, say Y. + +config MMC_SDHCI_XENON + tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver" + depends on MMC_SDHCI_PLTFM + help + This selects Marvell Xenon eMMC/SD/SDIO SDHCI. + If you have a controller with this interface, say Y or M here. + If unsure, say N. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index ec78296ef053..decb902dbe74 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -86,3 +86,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG endif + +obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o +sdhci-xenon-driver-y += sdhci-xenon.o diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c new file mode 100644 index 000000000000..b3f2b018fe3d --- /dev/null +++ b/drivers/mmc/host/sdhci-xenon.c @@ -0,0 +1,541 @@ +/* + * Driver for Marvell Xenon SDHC as a platform device + * + * Copyright (C) 2016 Marvell, All Rights Reserved. + * + * Author: Hu Ziji + * Date: 2016-8-24 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * Inspired by Jisheng Zhang + * Special thanks to Video BG4 project team. + */ + +#include +#include +#include +#include + +#include "sdhci-pltfm.h" +#include "sdhci-xenon.h" + +static int xenon_enable_internal_clk(struct sdhci_host *host) +{ + u32 reg; + ktime_t timeout; + + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + reg |= SDHCI_CLOCK_INT_EN; + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL); + /* Wait max 20 ms */ + timeout = ktime_add_ms(ktime_get(), 20); + while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(mmc_dev(host->mmc), "Internal clock never stabilised.\n"); + return -ETIMEDOUT; + } + usleep_range(900, 1100); + } + + return 0; +} + +/* Set SDCLK-off-while-idle */ +static void xenon_set_sdclk_off_idle(struct sdhci_host *host, + unsigned char sdhc_id, bool enable) +{ + u32 reg; + u32 mask; + + reg = sdhci_readl(host, XENON_SYS_OP_CTRL); + /* Get the bit shift basing on the SDHC index */ + mask = (0x1 << (XENON_SDCLK_IDLEOFF_ENABLE_SHIFT + sdhc_id)); + if (enable) + reg |= mask; + else + reg &= ~mask; + + sdhci_writel(host, reg, XENON_SYS_OP_CTRL); +} + +/* Enable/Disable the Auto Clock Gating function */ +static void xenon_set_acg(struct sdhci_host *host, bool enable) +{ + u32 reg; + + reg = sdhci_readl(host, XENON_SYS_OP_CTRL); + if (enable) + reg &= ~XENON_AUTO_CLKGATE_DISABLE_MASK; + else + reg |= XENON_AUTO_CLKGATE_DISABLE_MASK; + sdhci_writel(host, reg, XENON_SYS_OP_CTRL); +} + +/* Enable this SDHC */ +static void xenon_enable_sdhc(struct sdhci_host *host, + unsigned char sdhc_id) +{ + u32 reg; + + reg = sdhci_readl(host, XENON_SYS_OP_CTRL); + reg |= (BIT(sdhc_id) << XENON_SLOT_ENABLE_SHIFT); + sdhci_writel(host, reg, XENON_SYS_OP_CTRL); + + host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; + /* + * Force to clear BUS_TEST to + * skip bus_test_pre and bus_test_post + */ + host->mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST; +} + +/* Disable this SDHC */ +static void xenon_disable_sdhc(struct sdhci_host *host, + unsigned char sdhc_id) +{ + u32 reg; + + reg = sdhci_readl(host, XENON_SYS_OP_CTRL); + reg &= ~(BIT(sdhc_id) << XENON_SLOT_ENABLE_SHIFT); + sdhci_writel(host, reg, XENON_SYS_OP_CTRL); +} + +/* Enable Parallel Transfer Mode */ +static void xenon_enable_sdhc_parallel_tran(struct sdhci_host *host, + unsigned char sdhc_id) +{ + u32 reg; + + reg = sdhci_readl(host, XENON_SYS_EXT_OP_CTRL); + reg |= BIT(sdhc_id); + sdhci_writel(host, reg, XENON_SYS_EXT_OP_CTRL); +} + +/* Mask command conflict error */ +static void xenon_mask_cmd_conflict_err(struct sdhci_host *host) +{ + u32 reg; + + reg = sdhci_readl(host, XENON_SYS_EXT_OP_CTRL); + reg |= XENON_MASK_CMD_CONFLICT_ERR; + sdhci_writel(host, reg, XENON_SYS_EXT_OP_CTRL); +} + +static void xenon_retune_setup(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + + /* Disable the Re-Tuning Request functionality */ + reg = sdhci_readl(host, XENON_SLOT_RETUNING_REQ_CTRL); + reg &= ~XENON_RETUNING_COMPATIBLE; + sdhci_writel(host, reg, XENON_SLOT_RETUNING_REQ_CTRL); + + /* Disable the Re-tuning Interrupt */ + reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE); + reg &= ~SDHCI_INT_RETUNE; + sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE); + reg = sdhci_readl(host, SDHCI_INT_ENABLE); + reg &= ~SDHCI_INT_RETUNE; + sdhci_writel(host, reg, SDHCI_INT_ENABLE); + + /* Force to use Tuning Mode 1 */ + host->tuning_mode = SDHCI_TUNING_MODE_1; + /* Set re-tuning period */ + host->tuning_count = 1 << (priv->tuning_count - 1); +} + +/* + * Operations inside struct sdhci_ops + */ +/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */ +static void xenon_reset_exit(struct sdhci_host *host, + unsigned char sdhc_id, u8 mask) +{ + /* Only SOFTWARE RESET ALL will clear the register setting */ + if (!(mask & SDHCI_RESET_ALL)) + return; + + /* Disable tuning request and auto-retuning again */ + xenon_retune_setup(host); + + xenon_set_acg(host, true); + + xenon_set_sdclk_off_idle(host, sdhc_id, false); + + xenon_mask_cmd_conflict_err(host); +} + +static void xenon_reset(struct sdhci_host *host, u8 mask) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + + sdhci_reset(host, mask); + xenon_reset_exit(host, priv->sdhc_id, mask); +} + +/* + * Xenon defines different values for HS200 and HS400 + * in Host_Control_2 + */ +static void xenon_set_uhs_signaling(struct sdhci_host *host, + unsigned int timing) +{ + u16 ctrl_2; + + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); + /* Select Bus Speed Mode for host */ + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + if (timing == MMC_TIMING_MMC_HS200) + ctrl_2 |= XENON_CTRL_HS200; + else if (timing == MMC_TIMING_UHS_SDR104) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104; + else if (timing == MMC_TIMING_UHS_SDR12) + ctrl_2 |= SDHCI_CTRL_UHS_SDR12; + else if (timing == MMC_TIMING_UHS_SDR25) + ctrl_2 |= SDHCI_CTRL_UHS_SDR25; + else if (timing == MMC_TIMING_UHS_SDR50) + ctrl_2 |= SDHCI_CTRL_UHS_SDR50; + else if ((timing == MMC_TIMING_UHS_DDR50) || + (timing == MMC_TIMING_MMC_DDR52)) + ctrl_2 |= SDHCI_CTRL_UHS_DDR50; + else if (timing == MMC_TIMING_MMC_HS400) + ctrl_2 |= XENON_CTRL_HS400; + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); +} + +static const struct sdhci_ops sdhci_xenon_ops = { + .set_clock = sdhci_set_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = xenon_reset, + .set_uhs_signaling = xenon_set_uhs_signaling, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, +}; + +static const struct sdhci_pltfm_data sdhci_xenon_pdata = { + .ops = &sdhci_xenon_ops, + .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC | + SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER | + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, +}; + +/* + * Xenon Specific Operations in mmc_host_ops + */ +static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + + /* + * HS400/HS200/eMMC HS doesn't have Preset Value register. + * However, sdhci_set_ios will read HS400/HS200 Preset register. + * Disable Preset Value register for HS400/HS200. + * eMMC HS with preset_enabled set will trigger a bug in + * get_preset_value(). + */ + if ((ios->timing == MMC_TIMING_MMC_HS400) || + (ios->timing == MMC_TIMING_MMC_HS200) || + (ios->timing == MMC_TIMING_MMC_HS)) { + host->preset_enabled = false; + host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN; + host->flags &= ~SDHCI_PV_ENABLED; + + reg = sdhci_readw(host, SDHCI_HOST_CONTROL2); + reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE; + sdhci_writew(host, reg, SDHCI_HOST_CONTROL2); + } else { + host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN; + } + + sdhci_set_ios(mmc, ios); + + if (host->clock > XENON_DEFAULT_SDCLK_FREQ) + xenon_set_sdclk_off_idle(host, priv->sdhc_id, true); +} + +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + + /* + * Before SD/SDIO set signal voltage, SD bus clock should be + * disabled. However, sdhci_set_clock will also disable the Internal + * clock in mmc_set_signal_voltage(). + * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated. + * Thus here manually enable internal clock. + * + * After switch completes, it is unnecessary to disable internal clock, + * since keeping internal clock active obeys SD spec. + */ + xenon_enable_internal_clk(host); + + /* + * If Vqmmc is fixed on platform, vqmmc regulator should be unavailable. + * Thus SDHCI_CTRL_VDD_180 bit might not work then. + * Skip the standard voltage switch to avoid any issue. + */ + if (PTR_ERR(mmc->supply.vqmmc) == -ENODEV) + return 0; + + return sdhci_start_signal_voltage_switch(mmc, ios); +} + +/* + * Update card type. + * priv->init_card_type will be used in PHY timing adjustment. + */ +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + + /* Update card type*/ + priv->init_card_type = card->type; +} + +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->timing == MMC_TIMING_UHS_DDR50) + return 0; + + /* + * Currently force Xenon driver back to support mode 1 only, + * even though Xenon might claim to support mode 2 or mode 3. + * It requires more time to test mode 2/mode 3 on more platforms. + */ + if (host->tuning_mode != SDHCI_TUNING_MODE_1) + xenon_retune_setup(host); + + return sdhci_execute_tuning(mmc, opcode); +} + +static void xenon_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + u8 sdhc_id = priv->sdhc_id; + + sdhci_enable_sdio_irq(mmc, enable); + + if (enable) { + /* + * Set SDIO Card Inserted indication + * to enable detecting SDIO async irq. + */ + reg = sdhci_readl(host, XENON_SYS_CFG_INFO); + reg |= (1 << (sdhc_id + XENON_SLOT_TYPE_SDIO_SHIFT)); + sdhci_writel(host, reg, XENON_SYS_CFG_INFO); + } else { + /* Clear SDIO Card Inserted indication */ + reg = sdhci_readl(host, XENON_SYS_CFG_INFO); + reg &= ~(1 << (sdhc_id + XENON_SLOT_TYPE_SDIO_SHIFT)); + sdhci_writel(host, reg, XENON_SYS_CFG_INFO); + } +} + +static void xenon_replace_mmc_host_ops(struct sdhci_host *host) +{ + host->mmc_host_ops.set_ios = xenon_set_ios; + host->mmc_host_ops.start_signal_voltage_switch = + xenon_start_signal_voltage_switch; + host->mmc_host_ops.init_card = xenon_init_card; + host->mmc_host_ops.execute_tuning = xenon_execute_tuning; + host->mmc_host_ops.enable_sdio_irq = xenon_enable_sdio_irq; +} + +/* + * Parse Xenon specific DT properties: + * sdhc-id: the index of current SDHC. + * Refer to XENON_SYS_CFG_INFO register + * tun-count: the interval between re-tuning + */ +static int xenon_probe_dt(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sdhci_host *host = platform_get_drvdata(pdev); + struct mmc_host *mmc = host->mmc; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 sdhc_id, nr_sdhc; + u32 tuning_count; + + /* Disable HS200 on Armada AP806 */ + if (of_device_is_compatible(np, "marvell,armada-ap806-sdhci")) + host->quirks2 |= SDHCI_QUIRK2_BROKEN_HS200; + + sdhc_id = 0x0; + if (!of_property_read_u32(np, "marvell,xenon-sdhc-id", &sdhc_id)) { + nr_sdhc = sdhci_readl(host, XENON_SYS_CFG_INFO); + nr_sdhc &= XENON_NR_SUPPORTED_SLOT_MASK; + if (unlikely(sdhc_id > nr_sdhc)) { + dev_err(mmc_dev(mmc), "SDHC Index %d exceeds Number of SDHCs %d\n", + sdhc_id, nr_sdhc); + return -EINVAL; + } + } + priv->sdhc_id = sdhc_id; + + tuning_count = XENON_DEF_TUNING_COUNT; + if (!of_property_read_u32(np, "marvell,xenon-tun-count", + &tuning_count)) { + if (unlikely(tuning_count >= XENON_TMR_RETUN_NO_PRESENT)) { + dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n", + XENON_DEF_TUNING_COUNT); + tuning_count = XENON_DEF_TUNING_COUNT; + } + } + priv->tuning_count = tuning_count; + + return 0; +} + +static int xenon_sdhc_prepare(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + u8 sdhc_id = priv->sdhc_id; + + /* Enable SDHC */ + xenon_enable_sdhc(host, sdhc_id); + + /* Enable ACG */ + xenon_set_acg(host, true); + + /* Enable Parallel Transfer Mode */ + xenon_enable_sdhc_parallel_tran(host, sdhc_id); + + /* Disable SDCLK-Off-While-Idle before card init */ + xenon_set_sdclk_off_idle(host, sdhc_id, false); + + xenon_mask_cmd_conflict_err(host); + + return 0; +} + +static void xenon_sdhc_unprepare(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + u8 sdhc_id = priv->sdhc_id; + + /* disable SDHC */ + xenon_disable_sdhc(host, sdhc_id); +} + +static int xenon_probe(struct platform_device *pdev) +{ + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_host *host; + struct xenon_priv *priv; + int err; + + host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata, + sizeof(struct xenon_priv)); + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + priv = sdhci_pltfm_priv(pltfm_host); + + /* + * Link Xenon specific mmc_host_ops function, + * to replace standard ones in sdhci_ops. + */ + xenon_replace_mmc_host_ops(host); + + pltfm_host->clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(pltfm_host->clk)) { + err = PTR_ERR(pltfm_host->clk); + dev_err(&pdev->dev, "Failed to setup input clk: %d\n", err); + goto free_pltfm; + } + err = clk_prepare_enable(pltfm_host->clk); + if (err) + goto free_pltfm; + + err = mmc_of_parse(host->mmc); + if (err) + goto err_clk; + + sdhci_get_of_property(pdev); + + xenon_set_acg(host, false); + + /* Xenon specific dt parse */ + err = xenon_probe_dt(pdev); + if (err) + goto err_clk; + + err = xenon_sdhc_prepare(host); + if (err) + goto err_clk; + + err = sdhci_add_host(host); + if (err) + goto remove_sdhc; + + return 0; + +remove_sdhc: + xenon_sdhc_unprepare(host); +err_clk: + clk_disable_unprepare(pltfm_host->clk); +free_pltfm: + sdhci_pltfm_free(pdev); + return err; +} + +static int xenon_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + xenon_sdhc_unprepare(host); + + sdhci_remove_host(host, 0); + + clk_disable_unprepare(pltfm_host->clk); + + sdhci_pltfm_free(pdev); + + return 0; +} + +static const struct of_device_id sdhci_xenon_dt_ids[] = { + { .compatible = "marvell,armada-ap806-sdhci",}, + { .compatible = "marvell,armada-cp110-sdhci",}, + { .compatible = "marvell,armada-3700-sdhci",}, + {} +}; +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids); + +static struct platform_driver sdhci_xenon_driver = { + .driver = { + .name = "xenon-sdhci", + .of_match_table = sdhci_xenon_dt_ids, + .pm = &sdhci_pltfm_pmops, + }, + .probe = xenon_probe, + .remove = xenon_remove, +}; + +module_platform_driver(sdhci_xenon_driver); + +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC"); +MODULE_AUTHOR("Hu Ziji "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h new file mode 100644 index 000000000000..813585b45e8d --- /dev/null +++ b/drivers/mmc/host/sdhci-xenon.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 Marvell, All Rights Reserved. + * + * Author: Hu Ziji + * Date: 2016-8-24 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + */ +#ifndef SDHCI_XENON_H_ +#define SDHCI_XENON_H_ + +/* Register Offset of Xenon SDHC self-defined register */ +#define XENON_SYS_CFG_INFO 0x0104 +#define XENON_SLOT_TYPE_SDIO_SHIFT 24 +#define XENON_NR_SUPPORTED_SLOT_MASK 0x7 + +#define XENON_SYS_OP_CTRL 0x0108 +#define XENON_AUTO_CLKGATE_DISABLE_MASK BIT(20) +#define XENON_SDCLK_IDLEOFF_ENABLE_SHIFT 8 +#define XENON_SLOT_ENABLE_SHIFT 0 + +#define XENON_SYS_EXT_OP_CTRL 0x010C +#define XENON_MASK_CMD_CONFLICT_ERR BIT(8) + +#define XENON_SLOT_RETUNING_REQ_CTRL 0x0144 +/* retuning compatible */ +#define XENON_RETUNING_COMPATIBLE 0x1 + +/* Tuning Parameter */ +#define XENON_TMR_RETUN_NO_PRESENT 0xF +#define XENON_DEF_TUNING_COUNT 0x9 + +#define XENON_DEFAULT_SDCLK_FREQ 400000 + +/* Xenon specific Mode Select value */ +#define XENON_CTRL_HS200 0x5 +#define XENON_CTRL_HS400 0x6 + +struct xenon_priv { + unsigned char tuning_count; + /* idx of SDHC */ + u8 sdhc_id; + + /* + * eMMC/SD/SDIO require different register settings. + * Xenon driver has to recognize card type + * before mmc_host->card is not available. + * This field records the card type during init. + * It is updated in xenon_init_card(). + * + * It is only valid during initialization after it is updated. + * Do not access this variable in normal transfers after + * initialization completes. + */ + unsigned int init_card_type; +}; + +#endif From 06c8b667ff5b984ac3aa901c4928e4a427987a56 Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:23:00 +0200 Subject: [PATCH 118/148] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY. Multiple types of PHYs are supported. Add support to multiple types of PHYs init and configuration. Add register definitions of PHYs. Xenon PHY cannot fit in kernel common PHY framework. Xenon SDHC PHY register is a part of Xenon SDHC register set. Besides, MMC initialization has to call several PHY functions to complete timing setting. Those PHY setting functions have to access SDHC registers and know current MMC setting, such as bus width, clock frequency and speed mode. As a result, implement Xenon PHY in MMC host directory. Signed-off-by: Hu Ziji Signed-off-by: Gregory CLEMENT Signed-off-by: Ulf Hansson --- drivers/mmc/host/Makefile | 2 +- drivers/mmc/host/sdhci-xenon-phy.c | 727 +++++++++++++++++++++++++++++ drivers/mmc/host/sdhci-xenon.c | 9 +- drivers/mmc/host/sdhci-xenon.h | 39 ++ 4 files changed, 774 insertions(+), 3 deletions(-) create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index decb902dbe74..322df50a801d 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -88,4 +88,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y) endif obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o -sdhci-xenon-driver-y += sdhci-xenon.o +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c new file mode 100644 index 000000000000..b14544e91b65 --- /dev/null +++ b/drivers/mmc/host/sdhci-xenon-phy.c @@ -0,0 +1,727 @@ +/* + * PHY support for Xenon SDHC + * + * Copyright (C) 2016 Marvell, All Rights Reserved. + * + * Author: Hu Ziji + * Date: 2016-8-24 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + */ + +#include +#include +#include +#include + +#include "sdhci-pltfm.h" +#include "sdhci-xenon.h" + +/* Register base for eMMC PHY 5.0 Version */ +#define XENON_EMMC_5_0_PHY_REG_BASE 0x0160 +/* Register base for eMMC PHY 5.1 Version */ +#define XENON_EMMC_PHY_REG_BASE 0x0170 + +#define XENON_EMMC_PHY_TIMING_ADJUST XENON_EMMC_PHY_REG_BASE +#define XENON_EMMC_5_0_PHY_TIMING_ADJUST XENON_EMMC_5_0_PHY_REG_BASE +#define XENON_TIMING_ADJUST_SLOW_MODE BIT(29) +#define XENON_TIMING_ADJUST_SDIO_MODE BIT(28) +#define XENON_SAMPL_INV_QSP_PHASE_SELECT BIT(18) +#define XENON_SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18 +#define XENON_PHY_INITIALIZAION BIT(31) +#define XENON_WAIT_CYCLE_BEFORE_USING_MASK 0xF +#define XENON_WAIT_CYCLE_BEFORE_USING_SHIFT 12 +#define XENON_FC_SYNC_EN_DURATION_MASK 0xF +#define XENON_FC_SYNC_EN_DURATION_SHIFT 8 +#define XENON_FC_SYNC_RST_EN_DURATION_MASK 0xF +#define XENON_FC_SYNC_RST_EN_DURATION_SHIFT 4 +#define XENON_FC_SYNC_RST_DURATION_MASK 0xF +#define XENON_FC_SYNC_RST_DURATION_SHIFT 0 + +#define XENON_EMMC_PHY_FUNC_CONTROL (XENON_EMMC_PHY_REG_BASE + 0x4) +#define XENON_EMMC_5_0_PHY_FUNC_CONTROL \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x4) +#define XENON_ASYNC_DDRMODE_MASK BIT(23) +#define XENON_ASYNC_DDRMODE_SHIFT 23 +#define XENON_CMD_DDR_MODE BIT(16) +#define XENON_DQ_DDR_MODE_SHIFT 8 +#define XENON_DQ_DDR_MODE_MASK 0xFF +#define XENON_DQ_ASYNC_MODE BIT(4) + +#define XENON_EMMC_PHY_PAD_CONTROL (XENON_EMMC_PHY_REG_BASE + 0x8) +#define XENON_EMMC_5_0_PHY_PAD_CONTROL \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x8) +#define XENON_REC_EN_SHIFT 24 +#define XENON_REC_EN_MASK 0xF +#define XENON_FC_DQ_RECEN BIT(24) +#define XENON_FC_CMD_RECEN BIT(25) +#define XENON_FC_QSP_RECEN BIT(26) +#define XENON_FC_QSN_RECEN BIT(27) +#define XENON_OEN_QSN BIT(28) +#define XENON_AUTO_RECEN_CTRL BIT(30) +#define XENON_FC_ALL_CMOS_RECEIVER 0xF000 + +#define XENON_EMMC5_FC_QSP_PD BIT(18) +#define XENON_EMMC5_FC_QSP_PU BIT(22) +#define XENON_EMMC5_FC_CMD_PD BIT(17) +#define XENON_EMMC5_FC_CMD_PU BIT(21) +#define XENON_EMMC5_FC_DQ_PD BIT(16) +#define XENON_EMMC5_FC_DQ_PU BIT(20) + +#define XENON_EMMC_PHY_PAD_CONTROL1 (XENON_EMMC_PHY_REG_BASE + 0xC) +#define XENON_EMMC5_1_FC_QSP_PD BIT(9) +#define XENON_EMMC5_1_FC_QSP_PU BIT(25) +#define XENON_EMMC5_1_FC_CMD_PD BIT(8) +#define XENON_EMMC5_1_FC_CMD_PU BIT(24) +#define XENON_EMMC5_1_FC_DQ_PD 0xFF +#define XENON_EMMC5_1_FC_DQ_PU (0xFF << 16) + +#define XENON_EMMC_PHY_PAD_CONTROL2 (XENON_EMMC_PHY_REG_BASE + 0x10) +#define XENON_EMMC_5_0_PHY_PAD_CONTROL2 \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0xC) +#define XENON_ZNR_MASK 0x1F +#define XENON_ZNR_SHIFT 8 +#define XENON_ZPR_MASK 0x1F +/* Preferred ZNR and ZPR value vary between different boards. + * The specific ZNR and ZPR value should be defined here + * according to board actual timing. + */ +#define XENON_ZNR_DEF_VALUE 0xF +#define XENON_ZPR_DEF_VALUE 0xF + +#define XENON_EMMC_PHY_DLL_CONTROL (XENON_EMMC_PHY_REG_BASE + 0x14) +#define XENON_EMMC_5_0_PHY_DLL_CONTROL \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x10) +#define XENON_DLL_ENABLE BIT(31) +#define XENON_DLL_UPDATE_STROBE_5_0 BIT(30) +#define XENON_DLL_REFCLK_SEL BIT(30) +#define XENON_DLL_UPDATE BIT(23) +#define XENON_DLL_PHSEL1_SHIFT 24 +#define XENON_DLL_PHSEL0_SHIFT 16 +#define XENON_DLL_PHASE_MASK 0x3F +#define XENON_DLL_PHASE_90_DEGREE 0x1F +#define XENON_DLL_FAST_LOCK BIT(5) +#define XENON_DLL_GAIN2X BIT(3) +#define XENON_DLL_BYPASS_EN BIT(0) + +#define XENON_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x14) +#define XENON_EMMC_PHY_LOGIC_TIMING_ADJUST (XENON_EMMC_PHY_REG_BASE + 0x18) +#define XENON_LOGIC_TIMING_VALUE 0x00AA8977 + +/* + * List offset of PHY registers and some special register values + * in eMMC PHY 5.0 or eMMC PHY 5.1 + */ +struct xenon_emmc_phy_regs { + /* Offset of Timing Adjust register */ + u16 timing_adj; + /* Offset of Func Control register */ + u16 func_ctrl; + /* Offset of Pad Control register */ + u16 pad_ctrl; + /* Offset of Pad Control register 2 */ + u16 pad_ctrl2; + /* Offset of DLL Control register */ + u16 dll_ctrl; + /* Offset of Logic Timing Adjust register */ + u16 logic_timing_adj; + /* DLL Update Enable bit */ + u32 dll_update; +}; + +static const char * const phy_types[] = { + "emmc 5.0 phy", + "emmc 5.1 phy" +}; + +enum xenon_phy_type_enum { + EMMC_5_0_PHY, + EMMC_5_1_PHY, + NR_PHY_TYPES +}; + +static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = { + .timing_adj = XENON_EMMC_5_0_PHY_TIMING_ADJUST, + .func_ctrl = XENON_EMMC_5_0_PHY_FUNC_CONTROL, + .pad_ctrl = XENON_EMMC_5_0_PHY_PAD_CONTROL, + .pad_ctrl2 = XENON_EMMC_5_0_PHY_PAD_CONTROL2, + .dll_ctrl = XENON_EMMC_5_0_PHY_DLL_CONTROL, + .logic_timing_adj = XENON_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST, + .dll_update = XENON_DLL_UPDATE_STROBE_5_0, +}; + +static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = { + .timing_adj = XENON_EMMC_PHY_TIMING_ADJUST, + .func_ctrl = XENON_EMMC_PHY_FUNC_CONTROL, + .pad_ctrl = XENON_EMMC_PHY_PAD_CONTROL, + .pad_ctrl2 = XENON_EMMC_PHY_PAD_CONTROL2, + .dll_ctrl = XENON_EMMC_PHY_DLL_CONTROL, + .logic_timing_adj = XENON_EMMC_PHY_LOGIC_TIMING_ADJUST, + .dll_update = XENON_DLL_UPDATE, +}; + +/* + * eMMC PHY configuration and operations + */ +struct xenon_emmc_phy_params { + bool slow_mode; + + u8 znr; + u8 zpr; + + /* Nr of consecutive Sampling Points of a Valid Sampling Window */ + u8 nr_tun_times; + /* Divider for calculating Tuning Step */ + u8 tun_step_divider; +}; + +static int xenon_alloc_emmc_phy(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_params *params; + + params = devm_kzalloc(mmc_dev(host->mmc), sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + priv->phy_params = params; + if (priv->phy_type == EMMC_5_0_PHY) + priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs; + else + priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs; + + return 0; +} + +/* + * eMMC 5.0/5.1 PHY init/re-init. + * eMMC PHY init should be executed after: + * 1. SDCLK frequency changes. + * 2. SDCLK is stopped and re-enabled. + * 3. config in emmc_phy_regs->timing_adj and emmc_phy_regs->func_ctrl + * are changed + */ +static int xenon_emmc_phy_init(struct sdhci_host *host) +{ + u32 reg; + u32 wait, clock; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + + reg = sdhci_readl(host, phy_regs->timing_adj); + reg |= XENON_PHY_INITIALIZAION; + sdhci_writel(host, reg, phy_regs->timing_adj); + + /* Add duration of FC_SYNC_RST */ + wait = ((reg >> XENON_FC_SYNC_RST_DURATION_SHIFT) & + XENON_FC_SYNC_RST_DURATION_MASK); + /* Add interval between FC_SYNC_EN and FC_SYNC_RST */ + wait += ((reg >> XENON_FC_SYNC_RST_EN_DURATION_SHIFT) & + XENON_FC_SYNC_RST_EN_DURATION_MASK); + /* Add duration of asserting FC_SYNC_EN */ + wait += ((reg >> XENON_FC_SYNC_EN_DURATION_SHIFT) & + XENON_FC_SYNC_EN_DURATION_MASK); + /* Add duration of waiting for PHY */ + wait += ((reg >> XENON_WAIT_CYCLE_BEFORE_USING_SHIFT) & + XENON_WAIT_CYCLE_BEFORE_USING_MASK); + /* 4 additional bus clock and 4 AXI bus clock are required */ + wait += 8; + wait <<= 20; + + clock = host->clock; + if (!clock) + /* Use the possibly slowest bus frequency value */ + clock = XENON_LOWEST_SDCLK_FREQ; + /* get the wait time */ + wait /= clock; + wait++; + /* wait for host eMMC PHY init completes */ + udelay(wait); + + reg = sdhci_readl(host, phy_regs->timing_adj); + reg &= XENON_PHY_INITIALIZAION; + if (reg) { + dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n", + wait); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Enable eMMC PHY HW DLL + * DLL should be enabled and stable before HS200/SDR104 tuning, + * and before HS400 data strobe setting. + */ +static int xenon_emmc_phy_enable_dll(struct sdhci_host *host) +{ + u32 reg; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + ktime_t timeout; + + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR)) + return -EINVAL; + + reg = sdhci_readl(host, phy_regs->dll_ctrl); + if (reg & XENON_DLL_ENABLE) + return 0; + + /* Enable DLL */ + reg = sdhci_readl(host, phy_regs->dll_ctrl); + reg |= (XENON_DLL_ENABLE | XENON_DLL_FAST_LOCK); + + /* + * Set Phase as 90 degree, which is most common value. + * Might set another value if necessary. + * The granularity is 1 degree. + */ + reg &= ~((XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL0_SHIFT) | + (XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL1_SHIFT)); + reg |= ((XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL0_SHIFT) | + (XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL1_SHIFT)); + + reg &= ~XENON_DLL_BYPASS_EN; + reg |= phy_regs->dll_update; + if (priv->phy_type == EMMC_5_1_PHY) + reg &= ~XENON_DLL_REFCLK_SEL; + sdhci_writel(host, reg, phy_regs->dll_ctrl); + + /* Wait max 32 ms */ + timeout = ktime_add_ms(ktime_get(), 32); + while (!(sdhci_readw(host, XENON_SLOT_EXT_PRESENT_STATE) & + XENON_DLL_LOCK_STATE)) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n"); + return -ETIMEDOUT; + } + udelay(100); + } + return 0; +} + +/* + * Config to eMMC PHY to prepare for tuning. + * Enable HW DLL and set the TUNING_STEP + */ +static int xenon_emmc_phy_config_tuning(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_params *params = priv->phy_params; + u32 reg, tuning_step; + int ret; + + if (host->clock <= MMC_HIGH_52_MAX_DTR) + return -EINVAL; + + ret = xenon_emmc_phy_enable_dll(host); + if (ret) + return ret; + + /* Achieve TUNING_STEP with HW DLL help */ + reg = sdhci_readl(host, XENON_SLOT_DLL_CUR_DLY_VAL); + tuning_step = reg / params->tun_step_divider; + if (unlikely(tuning_step > XENON_TUNING_STEP_MASK)) { + dev_warn(mmc_dev(host->mmc), + "HS200 TUNING_STEP %d is larger than MAX value\n", + tuning_step); + tuning_step = XENON_TUNING_STEP_MASK; + } + + /* Set TUNING_STEP for later tuning */ + reg = sdhci_readl(host, XENON_SLOT_OP_STATUS_CTRL); + reg &= ~(XENON_TUN_CONSECUTIVE_TIMES_MASK << + XENON_TUN_CONSECUTIVE_TIMES_SHIFT); + reg |= (params->nr_tun_times << XENON_TUN_CONSECUTIVE_TIMES_SHIFT); + reg &= ~(XENON_TUNING_STEP_MASK << XENON_TUNING_STEP_SHIFT); + reg |= (tuning_step << XENON_TUNING_STEP_SHIFT); + sdhci_writel(host, reg, XENON_SLOT_OP_STATUS_CTRL); + + return 0; +} + +static void xenon_emmc_phy_disable_data_strobe(struct sdhci_host *host) +{ + u32 reg; + + /* Disable SDHC Data Strobe */ + reg = sdhci_readl(host, XENON_SLOT_EMMC_CTRL); + reg &= ~XENON_ENABLE_DATA_STROBE; + sdhci_writel(host, reg, XENON_SLOT_EMMC_CTRL); +} + +/* Set HS400 Data Strobe */ +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + + if (WARN_ON(host->timing != MMC_TIMING_MMC_HS400)) + return; + + if (host->clock <= MMC_HIGH_52_MAX_DTR) + return; + + dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n"); + + xenon_emmc_phy_enable_dll(host); + + /* Enable SDHC Data Strobe */ + reg = sdhci_readl(host, XENON_SLOT_EMMC_CTRL); + reg |= XENON_ENABLE_DATA_STROBE; + sdhci_writel(host, reg, XENON_SLOT_EMMC_CTRL); + + /* Set Data Strobe Pull down */ + if (priv->phy_type == EMMC_5_0_PHY) { + reg = sdhci_readl(host, XENON_EMMC_5_0_PHY_PAD_CONTROL); + reg |= XENON_EMMC5_FC_QSP_PD; + reg &= ~XENON_EMMC5_FC_QSP_PU; + sdhci_writel(host, reg, XENON_EMMC_5_0_PHY_PAD_CONTROL); + } else { + reg = sdhci_readl(host, XENON_EMMC_PHY_PAD_CONTROL1); + reg |= XENON_EMMC5_1_FC_QSP_PD; + reg &= ~XENON_EMMC5_1_FC_QSP_PU; + sdhci_writel(host, reg, XENON_EMMC_PHY_PAD_CONTROL1); + } +} + +/* + * If eMMC PHY Slow Mode is required in lower speed mode (SDCLK < 55MHz) + * in SDR mode, enable Slow Mode to bypass eMMC PHY. + * SDIO slower SDR mode also requires Slow Mode. + * + * If Slow Mode is enabled, return true. + * Otherwise, return false. + */ +static bool xenon_emmc_phy_slow_mode(struct sdhci_host *host, + unsigned char timing) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_params *params = priv->phy_params; + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + u32 reg; + int ret; + + if (host->clock > MMC_HIGH_52_MAX_DTR) + return false; + + reg = sdhci_readl(host, phy_regs->timing_adj); + /* When in slower SDR mode, enable Slow Mode for SDIO + * or when Slow Mode flag is set + */ + switch (timing) { + case MMC_TIMING_LEGACY: + /* + * If Slow Mode is required, enable Slow Mode by default + * in early init phase to avoid any potential issue. + */ + if (params->slow_mode) { + reg |= XENON_TIMING_ADJUST_SLOW_MODE; + ret = true; + } else { + reg &= ~XENON_TIMING_ADJUST_SLOW_MODE; + ret = false; + } + break; + case MMC_TIMING_UHS_SDR25: + case MMC_TIMING_UHS_SDR12: + case MMC_TIMING_SD_HS: + case MMC_TIMING_MMC_HS: + if ((priv->init_card_type == MMC_TYPE_SDIO) || + params->slow_mode) { + reg |= XENON_TIMING_ADJUST_SLOW_MODE; + ret = true; + break; + } + default: + reg &= ~XENON_TIMING_ADJUST_SLOW_MODE; + ret = false; + } + + sdhci_writel(host, reg, phy_regs->timing_adj); + return ret; +} + +/* + * Set-up eMMC 5.0/5.1 PHY. + * Specific configuration depends on the current speed mode in use. + */ +static void xenon_emmc_phy_set(struct sdhci_host *host, + unsigned char timing) +{ + u32 reg; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_params *params = priv->phy_params; + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs; + + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n"); + + /* Setup pad, set bit[28] and bits[26:24] */ + reg = sdhci_readl(host, phy_regs->pad_ctrl); + reg |= (XENON_FC_DQ_RECEN | XENON_FC_CMD_RECEN | + XENON_FC_QSP_RECEN | XENON_OEN_QSN); + /* All FC_XX_RECEIVCE should be set as CMOS Type */ + reg |= XENON_FC_ALL_CMOS_RECEIVER; + sdhci_writel(host, reg, phy_regs->pad_ctrl); + + /* Set CMD and DQ Pull Up */ + if (priv->phy_type == EMMC_5_0_PHY) { + reg = sdhci_readl(host, XENON_EMMC_5_0_PHY_PAD_CONTROL); + reg |= (XENON_EMMC5_FC_CMD_PU | XENON_EMMC5_FC_DQ_PU); + reg &= ~(XENON_EMMC5_FC_CMD_PD | XENON_EMMC5_FC_DQ_PD); + sdhci_writel(host, reg, XENON_EMMC_5_0_PHY_PAD_CONTROL); + } else { + reg = sdhci_readl(host, XENON_EMMC_PHY_PAD_CONTROL1); + reg |= (XENON_EMMC5_1_FC_CMD_PU | XENON_EMMC5_1_FC_DQ_PU); + reg &= ~(XENON_EMMC5_1_FC_CMD_PD | XENON_EMMC5_1_FC_DQ_PD); + sdhci_writel(host, reg, XENON_EMMC_PHY_PAD_CONTROL1); + } + + if (timing == MMC_TIMING_LEGACY) { + xenon_emmc_phy_slow_mode(host, timing); + goto phy_init; + } + + /* + * If SDIO card, set SDIO Mode + * Otherwise, clear SDIO Mode + */ + reg = sdhci_readl(host, phy_regs->timing_adj); + if (priv->init_card_type == MMC_TYPE_SDIO) + reg |= XENON_TIMING_ADJUST_SDIO_MODE; + else + reg &= ~XENON_TIMING_ADJUST_SDIO_MODE; + sdhci_writel(host, reg, phy_regs->timing_adj); + + if (xenon_emmc_phy_slow_mode(host, timing)) + goto phy_init; + + /* + * Set preferred ZNR and ZPR value + * The ZNR and ZPR value vary between different boards. + * Define them both in sdhci-xenon-emmc-phy.h. + */ + reg = sdhci_readl(host, phy_regs->pad_ctrl2); + reg &= ~((XENON_ZNR_MASK << XENON_ZNR_SHIFT) | XENON_ZPR_MASK); + reg |= ((params->znr << XENON_ZNR_SHIFT) | params->zpr); + sdhci_writel(host, reg, phy_regs->pad_ctrl2); + + /* + * When setting EMMC_PHY_FUNC_CONTROL register, + * SD clock should be disabled + */ + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + reg &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); + + reg = sdhci_readl(host, phy_regs->func_ctrl); + switch (timing) { + case MMC_TIMING_MMC_HS400: + reg |= (XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | + XENON_CMD_DDR_MODE; + reg &= ~XENON_DQ_ASYNC_MODE; + break; + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_MMC_DDR52: + reg |= (XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | + XENON_CMD_DDR_MODE | XENON_DQ_ASYNC_MODE; + break; + default: + reg &= ~((XENON_DQ_DDR_MODE_MASK << XENON_DQ_DDR_MODE_SHIFT) | + XENON_CMD_DDR_MODE); + reg |= XENON_DQ_ASYNC_MODE; + } + sdhci_writel(host, reg, phy_regs->func_ctrl); + + /* Enable bus clock */ + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + reg |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL); + + if (timing == MMC_TIMING_MMC_HS400) + /* Hardware team recommend a value for HS400 */ + sdhci_writel(host, XENON_LOGIC_TIMING_VALUE, + phy_regs->logic_timing_adj); + else + xenon_emmc_phy_disable_data_strobe(host); + +phy_init: + xenon_emmc_phy_init(host); + + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n"); +} + +static int xenon_emmc_phy_parse_param_dt(struct sdhci_host *host, + struct device_node *np, + struct xenon_emmc_phy_params *params) +{ + u32 value; + + params->slow_mode = false; + if (of_property_read_bool(np, "marvell,xenon-phy-slow-mode")) + params->slow_mode = true; + + params->znr = XENON_ZNR_DEF_VALUE; + if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value)) + params->znr = value & XENON_ZNR_MASK; + + params->zpr = XENON_ZPR_DEF_VALUE; + if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value)) + params->zpr = value & XENON_ZPR_MASK; + + params->nr_tun_times = XENON_TUN_CONSECUTIVE_TIMES; + if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun", + &value)) + params->nr_tun_times = value & XENON_TUN_CONSECUTIVE_TIMES_MASK; + + params->tun_step_divider = XENON_TUNING_STEP_DIVIDER; + if (!of_property_read_u32(np, "marvell,xenon-phy-tun-step-divider", + &value)) + params->tun_step_divider = value & 0xFF; + + return 0; +} + +/* + * Setting PHY when card is working in High Speed Mode. + * HS400 set data strobe line. + * HS200/SDR104 set tuning config to prepare for tuning. + */ +static int xenon_hs_delay_adj(struct sdhci_host *host) +{ + int ret = 0; + + if (WARN_ON(host->clock <= XENON_DEFAULT_SDCLK_FREQ)) + return -EINVAL; + + switch (host->timing) { + case MMC_TIMING_MMC_HS400: + xenon_emmc_phy_strobe_delay_adj(host); + return 0; + case MMC_TIMING_MMC_HS200: + case MMC_TIMING_UHS_SDR104: + return xenon_emmc_phy_config_tuning(host); + case MMC_TIMING_MMC_DDR52: + case MMC_TIMING_UHS_DDR50: + /* + * DDR Mode requires driver to scan Sampling Fixed Delay Line, + * to find out a perfect operation sampling point. + * It is hard to implement such a scan in host driver + * since initiating commands by host driver is not safe. + * Thus so far just keep PHY Sampling Fixed Delay in + * default value of DDR mode. + * + * If any timing issue occurs in DDR mode on Marvell products, + * please contact maintainer for internal support in Marvell. + */ + dev_warn_once(mmc_dev(host->mmc), "Timing issue might occur in DDR mode\n"); + return 0; + } + + return ret; +} + +/* + * Adjust PHY setting. + * PHY setting should be adjusted when SDCLK frequency, Bus Width + * or Speed Mode is changed. + * Additional config are required when card is working in High Speed mode, + * after leaving Legacy Mode. + */ +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + int ret = 0; + + if (!host->clock) { + priv->clock = 0; + return 0; + } + + /* + * The timing, frequency or bus width is changed, + * better to set eMMC PHY based on current setting + * and adjust Xenon SDHC delay. + */ + if ((host->clock == priv->clock) && + (ios->bus_width == priv->bus_width) && + (ios->timing == priv->timing)) + return 0; + + xenon_emmc_phy_set(host, ios->timing); + + /* Update the record */ + priv->bus_width = ios->bus_width; + + priv->timing = ios->timing; + priv->clock = host->clock; + + /* Legacy mode is a special case */ + if (ios->timing == MMC_TIMING_LEGACY) + return 0; + + if (host->clock > XENON_DEFAULT_SDCLK_FREQ) + ret = xenon_hs_delay_adj(host); + return ret; +} + +void xenon_clean_phy(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + + kfree(priv->phy_params); +} + +static int xenon_add_phy(struct device_node *np, struct sdhci_host *host, + const char *phy_name) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + int i, ret; + + for (i = 0; i < NR_PHY_TYPES; i++) { + if (!strcmp(phy_name, phy_types[i])) { + priv->phy_type = i; + break; + } + } + if (i == NR_PHY_TYPES) { + dev_err(mmc_dev(host->mmc), + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n", + phy_name); + priv->phy_type = EMMC_5_1_PHY; + } + + ret = xenon_alloc_emmc_phy(host); + if (ret) + return ret; + + ret = xenon_emmc_phy_parse_param_dt(host, np, priv->phy_params); + if (ret) + xenon_clean_phy(host); + + return ret; +} + +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host) +{ + const char *phy_type = NULL; + + if (!of_property_read_string(np, "marvell,xenon-phy-type", &phy_type)) + return xenon_add_phy(np, host, phy_type); + + return xenon_add_phy(np, host, "emmc 5.1 phy"); +} diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c index b3f2b018fe3d..36e22bd2b8cc 100644 --- a/drivers/mmc/host/sdhci-xenon.c +++ b/drivers/mmc/host/sdhci-xenon.c @@ -257,6 +257,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) } sdhci_set_ios(mmc, ios); + xenon_phy_adj(host, ios); if (host->clock > XENON_DEFAULT_SDCLK_FREQ) xenon_set_sdclk_off_idle(host, priv->sdhc_id, true); @@ -401,7 +402,7 @@ static int xenon_probe_dt(struct platform_device *pdev) } priv->tuning_count = tuning_count; - return 0; + return xenon_phy_parse_dt(np, host); } static int xenon_sdhc_prepare(struct sdhci_host *host) @@ -483,7 +484,7 @@ static int xenon_probe(struct platform_device *pdev) err = xenon_sdhc_prepare(host); if (err) - goto err_clk; + goto clean_phy_param; err = sdhci_add_host(host); if (err) @@ -493,6 +494,8 @@ static int xenon_probe(struct platform_device *pdev) remove_sdhc: xenon_sdhc_unprepare(host); +clean_phy_param: + xenon_clean_phy(host); err_clk: clk_disable_unprepare(pltfm_host->clk); free_pltfm: @@ -505,6 +508,8 @@ static int xenon_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + xenon_clean_phy(host); + xenon_sdhc_unprepare(host); sdhci_remove_host(host, 0); diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h index 813585b45e8d..b29d45358de8 100644 --- a/drivers/mmc/host/sdhci-xenon.h +++ b/drivers/mmc/host/sdhci-xenon.h @@ -24,15 +24,32 @@ #define XENON_SYS_EXT_OP_CTRL 0x010C #define XENON_MASK_CMD_CONFLICT_ERR BIT(8) +#define XENON_SLOT_OP_STATUS_CTRL 0x0128 +#define XENON_TUN_CONSECUTIVE_TIMES_SHIFT 16 +#define XENON_TUN_CONSECUTIVE_TIMES_MASK 0x7 +#define XENON_TUN_CONSECUTIVE_TIMES 0x4 +#define XENON_TUNING_STEP_SHIFT 12 +#define XENON_TUNING_STEP_MASK 0xF +#define XENON_TUNING_STEP_DIVIDER BIT(6) + +#define XENON_SLOT_EMMC_CTRL 0x0130 +#define XENON_ENABLE_DATA_STROBE BIT(24) + #define XENON_SLOT_RETUNING_REQ_CTRL 0x0144 /* retuning compatible */ #define XENON_RETUNING_COMPATIBLE 0x1 +#define XENON_SLOT_EXT_PRESENT_STATE 0x014C +#define XENON_DLL_LOCK_STATE 0x1 + +#define XENON_SLOT_DLL_CUR_DLY_VAL 0x0150 + /* Tuning Parameter */ #define XENON_TMR_RETUN_NO_PRESENT 0xF #define XENON_DEF_TUNING_COUNT 0x9 #define XENON_DEFAULT_SDCLK_FREQ 400000 +#define XENON_LOWEST_SDCLK_FREQ 100000 /* Xenon specific Mode Select value */ #define XENON_CTRL_HS200 0x5 @@ -55,6 +72,28 @@ struct xenon_priv { * initialization completes. */ unsigned int init_card_type; + + /* + * The bus_width, timing, and clock fields in below + * record the current ios setting of Xenon SDHC. + * Driver will adjust PHY setting if any change to + * ios affects PHY timing. + */ + unsigned char bus_width; + unsigned char timing; + unsigned int clock; + + int phy_type; + /* + * Contains board-specific PHY parameters + * passed from device tree. + */ + void *phy_params; + struct xenon_emmc_phy_regs *emmc_phy_regs; }; +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios); +void xenon_clean_phy(struct sdhci_host *host); +int xenon_phy_parse_dt(struct device_node *np, + struct sdhci_host *host); #endif From 298269c602b54dd56e1424cb8e706fc30bc7d489 Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:23:01 +0200 Subject: [PATCH 119/148] mmc: sdhci-xenon: Add SoC PHY PAD voltage control Some SoCs have PHY PAD outside Xenon IP. PHY PAD voltage should match signalling voltage in use. Add generic SoC PHY PAD voltage control interface. Implement Aramda-3700 SoC PHY PAD voltage control. Signed-off-by: Hu Ziji Tested-by: Russell King Signed-off-by: Gregory CLEMENT Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-xenon-phy.c | 110 ++++++++++++++++++++++++++++- drivers/mmc/host/sdhci-xenon.c | 2 + drivers/mmc/host/sdhci-xenon.h | 2 + 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c index b14544e91b65..4bdbcd3f2645 100644 --- a/drivers/mmc/host/sdhci-xenon-phy.c +++ b/drivers/mmc/host/sdhci-xenon-phy.c @@ -143,6 +143,21 @@ enum xenon_phy_type_enum { NR_PHY_TYPES }; +enum soc_pad_ctrl_type { + SOC_PAD_SD, + SOC_PAD_FIXED_1_8V, +}; + +struct soc_pad_ctrl { + /* Register address of SoC PHY PAD ctrl */ + void __iomem *reg; + /* SoC PHY PAD ctrl type */ + enum soc_pad_ctrl_type pad_type; + /* SoC specific operation to set SoC PHY PAD */ + void (*set_soc_pad)(struct sdhci_host *host, + unsigned char signal_voltage); +}; + static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = { .timing_adj = XENON_EMMC_5_0_PHY_TIMING_ADJUST, .func_ctrl = XENON_EMMC_5_0_PHY_FUNC_CONTROL, @@ -176,6 +191,8 @@ struct xenon_emmc_phy_params { u8 nr_tun_times; /* Divider for calculating Tuning Step */ u8 tun_step_divider; + + struct soc_pad_ctrl pad_ctrl; }; static int xenon_alloc_emmc_phy(struct sdhci_host *host) @@ -254,6 +271,45 @@ static int xenon_emmc_phy_init(struct sdhci_host *host) return 0; } +#define ARMADA_3700_SOC_PAD_1_8V 0x1 +#define ARMADA_3700_SOC_PAD_3_3V 0x0 + +static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host, + unsigned char signal_voltage) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_params *params = priv->phy_params; + + if (params->pad_ctrl.pad_type == SOC_PAD_FIXED_1_8V) { + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg); + } else if (params->pad_ctrl.pad_type == SOC_PAD_SD) { + if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg); + else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) + writel(ARMADA_3700_SOC_PAD_3_3V, params->pad_ctrl.reg); + } +} + +/* + * Set SoC PHY voltage PAD control register, + * according to the operation voltage on PAD. + * The detailed operation depends on SoC implementation. + */ +static void xenon_emmc_phy_set_soc_pad(struct sdhci_host *host, + unsigned char signal_voltage) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct xenon_emmc_phy_params *params = priv->phy_params; + + if (!params->pad_ctrl.reg) + return; + + if (params->pad_ctrl.set_soc_pad) + params->pad_ctrl.set_soc_pad(host, signal_voltage); +} + /* * Enable eMMC PHY HW DLL * DLL should be enabled and stable before HS200/SDR104 tuning, @@ -562,6 +618,51 @@ phy_init: dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n"); } +static int get_dt_pad_ctrl_data(struct sdhci_host *host, + struct device_node *np, + struct xenon_emmc_phy_params *params) +{ + int ret = 0; + const char *name; + struct resource iomem; + + if (of_device_is_compatible(np, "marvell,armada-3700-sdhci")) + params->pad_ctrl.set_soc_pad = armada_3700_soc_pad_voltage_set; + else + return 0; + + if (of_address_to_resource(np, 1, &iomem)) { + dev_err(mmc_dev(host->mmc), "Unable to find SoC PAD ctrl register address for %s\n", + np->name); + return -EINVAL; + } + + params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc), + &iomem); + if (IS_ERR(params->pad_ctrl.reg)) { + dev_err(mmc_dev(host->mmc), "Unable to get SoC PHY PAD ctrl register for %s\n", + np->name); + return PTR_ERR(params->pad_ctrl.reg); + } + + ret = of_property_read_string(np, "marvell,pad-type", &name); + if (ret) { + dev_err(mmc_dev(host->mmc), "Unable to determine SoC PHY PAD ctrl type\n"); + return ret; + } + if (!strcmp(name, "sd")) { + params->pad_ctrl.pad_type = SOC_PAD_SD; + } else if (!strcmp(name, "fixed-1-8v")) { + params->pad_ctrl.pad_type = SOC_PAD_FIXED_1_8V; + } else { + dev_err(mmc_dev(host->mmc), "Unsupported SoC PHY PAD ctrl type %s\n", + name); + return -EINVAL; + } + + return ret; +} + static int xenon_emmc_phy_parse_param_dt(struct sdhci_host *host, struct device_node *np, struct xenon_emmc_phy_params *params) @@ -590,7 +691,14 @@ static int xenon_emmc_phy_parse_param_dt(struct sdhci_host *host, &value)) params->tun_step_divider = value & 0xFF; - return 0; + return get_dt_pad_ctrl_data(host, np, params); +} + +/* Set SoC PHY Voltage PAD */ +void xenon_soc_pad_ctrl(struct sdhci_host *host, + unsigned char signal_voltage) +{ + xenon_emmc_phy_set_soc_pad(host, signal_voltage); } /* diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c index 36e22bd2b8cc..8e56b9ccfb39 100644 --- a/drivers/mmc/host/sdhci-xenon.c +++ b/drivers/mmc/host/sdhci-xenon.c @@ -280,6 +280,8 @@ static int xenon_start_signal_voltage_switch(struct mmc_host *mmc, */ xenon_enable_internal_clk(host); + xenon_soc_pad_ctrl(host, ios->signal_voltage); + /* * If Vqmmc is fixed on platform, vqmmc regulator should be unavailable. * Thus SDHCI_CTRL_VDD_180 bit might not work then. diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h index b29d45358de8..6e6523ea01ce 100644 --- a/drivers/mmc/host/sdhci-xenon.h +++ b/drivers/mmc/host/sdhci-xenon.h @@ -96,4 +96,6 @@ int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios); void xenon_clean_phy(struct sdhci_host *host); int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host); +void xenon_soc_pad_ctrl(struct sdhci_host *host, + unsigned char signal_voltage); #endif From 511fc93d2ceb71d92071bdc950c4959ed3ac741b Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Thu, 30 Mar 2017 17:23:02 +0200 Subject: [PATCH 120/148] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host Controller drivers. Signed-off-by: Hu Ziji Signed-off-by: Gregory CLEMENT Signed-off-by: Ulf Hansson --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2816dd9114cf..a668480010f4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7898,6 +7898,13 @@ M: Nicolas Pitre S: Odd Fixes F: drivers/mmc/host/mvsdio.* +MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER +M: Hu Ziji +L: linux-mmc@vger.kernel.org +S: Supported +F: drivers/mmc/host/sdhci-xenon* +F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt + MATROX FRAMEBUFFER DRIVER L: linux-fbdev@vger.kernel.org S: Orphan From ec5ab8933772c87f24ad62a4a602fe8949f423c2 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 10 Apr 2017 16:54:17 +0300 Subject: [PATCH 121/148] mmc: host: omap_hsmmc: checking for NULL instead of IS_ERR() devm_pinctrl_get() returns error pointers, it never returns NULL. Fixes: 455e5cd6f736 ("mmc: omap_hsmmc: Pin remux workaround to support SDIO interrupt on AM335x") Signed-off-by: Dan Carpenter Reviewed-by: Kishon Vijay Abraham I Signed-off-by: Ulf Hansson --- drivers/mmc/host/omap_hsmmc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index bf64cf23aa59..8c39dccacf39 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -1761,8 +1761,8 @@ static int omap_hsmmc_configure_wake_irq(struct omap_hsmmc_host *host) */ if (host->pdata->controller_flags & OMAP_HSMMC_SWAKEUP_MISSING) { struct pinctrl *p = devm_pinctrl_get(host->dev); - if (!p) { - ret = -ENODEV; + if (IS_ERR(p)) { + ret = PTR_ERR(p); goto err_free_irq; } if (IS_ERR(pinctrl_lookup_state(p, PINCTRL_STATE_DEFAULT))) { From 861183f115cd80db7efebf5516f4e7a424c13abd Mon Sep 17 00:00:00 2001 From: Piotr Sroka Date: Tue, 11 Apr 2017 12:13:38 +0100 Subject: [PATCH 122/148] mmc: sdhci-cadence: add parsing sdhci properties Add calling sdhci_get_of_property function to parse sdhci properties. Signed-off-by: Piotr Sroka Acked-by: Adrian Hunter Reviewed-by: Masahiro Yamada Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 7797e98a7541..19d5698244b5 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -330,6 +330,8 @@ static int sdhci_cdns_probe(struct platform_device *pdev) host->mmc_host_ops.hs400_enhanced_strobe = sdhci_cdns_hs400_enhanced_strobe; + sdhci_get_of_property(pdev); + ret = mmc_of_parse(host->mmc); if (ret) goto free; From 06c9ccb78e68e2e9b69e736fc0a39fb13be49b74 Mon Sep 17 00:00:00 2001 From: "Winkler, Tomas" Date: Sun, 2 Apr 2017 23:56:03 +0300 Subject: [PATCH 123/148] mmc: core: add proper be32 annotation Annotate big endian values correctly and make sparse happy. In mmc_app_send_scr remove scr function parameter as it was updating card->raw_scr anyway. Signed-off-by: Tomas Winkler Signed-off-by: Ulf Hansson --- drivers/mmc/core/mmc_ops.c | 8 ++++---- drivers/mmc/core/sd.c | 4 ++-- drivers/mmc/core/sd_ops.c | 19 +++++++++---------- drivers/mmc/core/sd_ops.h | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 24c58d24c19a..78f75f00efc5 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -305,7 +305,7 @@ mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, int mmc_send_csd(struct mmc_card *card, u32 *csd) { int ret, i; - u32 *csd_tmp; + __be32 *csd_tmp; if (!mmc_host_is_spi(card->host)) return mmc_send_cxd_native(card->host, card->rca << 16, @@ -319,7 +319,7 @@ int mmc_send_csd(struct mmc_card *card, u32 *csd) if (ret) goto err; - for (i = 0;i < 4;i++) + for (i = 0; i < 4; i++) csd[i] = be32_to_cpu(csd_tmp[i]); err: @@ -330,7 +330,7 @@ err: int mmc_send_cid(struct mmc_host *host, u32 *cid) { int ret, i; - u32 *cid_tmp; + __be32 *cid_tmp; if (!mmc_host_is_spi(host)) { if (!host->card) @@ -347,7 +347,7 @@ int mmc_send_cid(struct mmc_host *host, u32 *cid) if (ret) goto err; - for (i = 0;i < 4;i++) + for (i = 0; i < 4; i++) cid[i] = be32_to_cpu(cid_tmp[i]); err: diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 89531b48ae84..d109634fbfce 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -225,7 +225,7 @@ static int mmc_decode_scr(struct mmc_card *card) static int mmc_read_ssr(struct mmc_card *card) { unsigned int au, es, et, eo; - u32 *raw_ssr; + __be32 *raw_ssr; int i; if (!(card->csd.cmdclass & CCC_APP_SPEC)) { @@ -853,7 +853,7 @@ int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card, /* * Fetch SCR from card. */ - err = mmc_app_send_scr(card, card->raw_scr); + err = mmc_app_send_scr(card); if (err) return err; diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 9d5824a37586..47056d8d1bac 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -232,14 +232,14 @@ int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) return 0; } -int mmc_app_send_scr(struct mmc_card *card, u32 *scr) +int mmc_app_send_scr(struct mmc_card *card) { int err; struct mmc_request mrq = {}; struct mmc_command cmd = {}; struct mmc_data data = {}; struct scatterlist sg; - void *data_buf; + __be32 *scr; /* NOTE: caller guarantees scr is heap-allocated */ @@ -250,8 +250,8 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) /* dma onto stack is unsafe/nonportable, but callers to this * routine normally provide temporary on-stack buffers ... */ - data_buf = kmalloc(sizeof(card->raw_scr), GFP_KERNEL); - if (data_buf == NULL) + scr = kmalloc(sizeof(card->raw_scr), GFP_KERNEL); + if (!scr) return -ENOMEM; mrq.cmd = &cmd; @@ -267,23 +267,22 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) data.sg = &sg; data.sg_len = 1; - sg_init_one(&sg, data_buf, 8); + sg_init_one(&sg, scr, 8); mmc_set_data_timeout(&data, card); mmc_wait_for_req(card->host, &mrq); - memcpy(scr, data_buf, sizeof(card->raw_scr)); - kfree(data_buf); + card->raw_scr[0] = be32_to_cpu(scr[0]); + card->raw_scr[1] = be32_to_cpu(scr[1]); + + kfree(scr); if (cmd.error) return cmd.error; if (data.error) return data.error; - scr[0] = be32_to_cpu(scr[0]); - scr[1] = be32_to_cpu(scr[1]); - return 0; } diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h index 784f8e6b6baa..0e6c3d51e66d 100644 --- a/drivers/mmc/core/sd_ops.h +++ b/drivers/mmc/core/sd_ops.h @@ -22,7 +22,7 @@ int mmc_app_set_bus_width(struct mmc_card *card, int width); int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr); int mmc_send_if_cond(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, u32 *scr); +int mmc_app_send_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); From c08bcb6c90068d8eacffdb8b5f77916de5481a72 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Tue, 4 Apr 2017 21:03:22 +0200 Subject: [PATCH 124/148] mmc: meson-gx: introduce struct meson_tuning_params Introduce struct meson_tuning_params for storing the clock phase configurations. There's no functional change because tx and rx clock phase were implicitely set to CLK_PHASE_0 before. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 0036680bad9e..3a6e51c8c5d2 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -49,6 +49,8 @@ #define CLK_SRC_PLL 1 /* FCLK_DIV2 */ #define CLK_SRC_PLL_RATE 1000000000 #define CLK_CORE_PHASE_MASK GENMASK(9, 8) +#define CLK_TX_PHASE_MASK GENMASK(11, 10) +#define CLK_RX_PHASE_MASK GENMASK(13, 12) #define CLK_PHASE_0 0 #define CLK_PHASE_90 1 #define CLK_PHASE_180 2 @@ -111,6 +113,12 @@ #define SD_EMMC_CFG_CMD_GAP 16 /* in clock cycles */ #define MUX_CLK_NUM_PARENTS 2 +struct meson_tuning_params { + u8 core_phase; + u8 tx_phase; + u8 rx_phase; +}; + struct meson_host { struct device *dev; struct mmc_host *mmc; @@ -130,6 +138,7 @@ struct meson_host { void *bounce_buf; dma_addr_t bounce_dma_addr; + struct meson_tuning_params tp; bool vqmmc_enabled; }; @@ -312,7 +321,9 @@ static int meson_mmc_clk_init(struct meson_host *host) /* init SD_EMMC_CLOCK to sane defaults w/min clock rate */ clk_reg = 0; - clk_reg |= FIELD_PREP(CLK_CORE_PHASE_MASK, CLK_PHASE_180); + clk_reg |= FIELD_PREP(CLK_CORE_PHASE_MASK, host->tp.core_phase); + clk_reg |= FIELD_PREP(CLK_TX_PHASE_MASK, host->tp.tx_phase); + clk_reg |= FIELD_PREP(CLK_RX_PHASE_MASK, host->tp.rx_phase); clk_reg |= FIELD_PREP(CLK_SRC_MASK, CLK_SRC_XTAL); clk_reg |= FIELD_PREP(CLK_DIV_MASK, CLK_DIV_MAX); clk_reg &= ~CLK_ALWAYS_ON; @@ -757,6 +768,10 @@ static int meson_mmc_probe(struct platform_device *pdev) if (ret) goto free_host; + host->tp.core_phase = CLK_PHASE_180; + host->tp.tx_phase = CLK_PHASE_0; + host->tp.rx_phase = CLK_PHASE_0; + ret = meson_mmc_clk_init(host); if (ret) goto err_core_clk; From 0b6ed71c3e95abf6cd1dbfc53919defdce3449f5 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Tue, 4 Apr 2017 21:11:17 +0200 Subject: [PATCH 125/148] mmc: meson-gx: add basic tuning for rx clock phase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds basic tuning which changes the rx clock phase only until a working setting is found. On a Odroid C2 with 128GB eMMC card and 200 MHz MMC clock only 180° rx clock phase make the system boot w/o CRC errors. With other MMC devices / clock speeds this might be different, therefore don't change the driver config in general. When retuning skip the currently active parameter set. This avoids the current problematic config to be chosen again if it causes CRC errors just occasionally. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 3a6e51c8c5d2..eb5ee68dbd46 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -349,6 +349,31 @@ static int meson_mmc_clk_init(struct meson_host *host) return ret; } +static void meson_mmc_set_tuning_params(struct mmc_host *mmc) +{ + struct meson_host *host = mmc_priv(mmc); + u32 regval; + + /* stop clock */ + regval = readl(host->regs + SD_EMMC_CFG); + regval |= CFG_STOP_CLOCK; + writel(regval, host->regs + SD_EMMC_CFG); + + regval = readl(host->regs + SD_EMMC_CLOCK); + regval &= ~CLK_CORE_PHASE_MASK; + regval |= FIELD_PREP(CLK_CORE_PHASE_MASK, host->tp.core_phase); + regval &= ~CLK_TX_PHASE_MASK; + regval |= FIELD_PREP(CLK_TX_PHASE_MASK, host->tp.tx_phase); + regval &= ~CLK_RX_PHASE_MASK; + regval |= FIELD_PREP(CLK_RX_PHASE_MASK, host->tp.rx_phase); + writel(regval, host->regs + SD_EMMC_CLOCK); + + /* start clock */ + regval = readl(host->regs + SD_EMMC_CFG); + regval &= ~CFG_STOP_CLOCK; + writel(regval, host->regs + SD_EMMC_CFG); +} + static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct meson_host *host = mmc_priv(mmc); @@ -682,6 +707,29 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) return IRQ_HANDLED; } +static int meson_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct meson_host *host = mmc_priv(mmc); + struct meson_tuning_params tp_old = host->tp; + int ret = -EINVAL, i, cmd_error; + + dev_info(mmc_dev(mmc), "(re)tuning...\n"); + + for (i = CLK_PHASE_0; i <= CLK_PHASE_270; i++) { + host->tp.rx_phase = i; + /* exclude the active parameter set if retuning */ + if (!memcmp(&tp_old, &host->tp, sizeof(tp_old)) && + mmc->doing_retune) + continue; + meson_mmc_set_tuning_params(mmc); + ret = mmc_send_tuning(mmc, opcode, &cmd_error); + if (!ret) + break; + } + + return ret; +} + /* * NOTE: we only need this until the GPIO/pinctrl driver can handle * interrupts. For now, the MMC core will use this for polling. @@ -712,6 +760,7 @@ static const struct mmc_host_ops meson_mmc_ops = { .request = meson_mmc_request, .set_ios = meson_mmc_set_ios, .get_cd = meson_mmc_get_cd, + .execute_tuning = meson_mmc_execute_tuning, }; static int meson_mmc_probe(struct platform_device *pdev) From 79ed05e329c34c68ab2a87f729e666fcf208fdad Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 7 Apr 2017 21:22:44 +0200 Subject: [PATCH 126/148] mmc: meson-gx: add support for descriptor chain mode So far a bounce buffer is used to serialize the scatterlist(s). This overhead can be avoided by switching to descriptor chain mode. As result the performance is drastically improved. On a Odroid-C2 with a 128 GB eMMC module raw reads reach 140 MB/s. Prerequisite for descriptor chain mode is that all scatterlist buffers are 8 byte aligned for 64-bit DMA. That's not always the case, at least the brcmfmac SDIO WiFi driver is known to cause problems. Therefore, for each request, check upfront whether all scatterlist buffers are 8 byte aligned and fall back to bounce buffer mode if that's not the case. Signed-off-by: Heiner Kallweit Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 172 +++++++++++++++++++++++++++++--- 1 file changed, 156 insertions(+), 16 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index eb5ee68dbd46..1842ed341af1 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -111,6 +111,11 @@ #define SD_EMMC_CMD_TIMEOUT 1024 /* in ms */ #define SD_EMMC_CMD_TIMEOUT_DATA 4096 /* in ms */ #define SD_EMMC_CFG_CMD_GAP 16 /* in clock cycles */ +#define SD_EMMC_DESC_BUF_LEN PAGE_SIZE + +#define SD_EMMC_PRE_REQ_DONE BIT(0) +#define SD_EMMC_DESC_CHAIN_MODE BIT(1) + #define MUX_CLK_NUM_PARENTS 2 struct meson_tuning_params { @@ -119,6 +124,13 @@ struct meson_tuning_params { u8 rx_phase; }; +struct sd_emmc_desc { + u32 cmd_cfg; + u32 cmd_arg; + u32 cmd_data; + u32 cmd_resp; +}; + struct meson_host { struct device *dev; struct mmc_host *mmc; @@ -137,18 +149,13 @@ struct meson_host { unsigned int bounce_buf_size; void *bounce_buf; dma_addr_t bounce_dma_addr; + struct sd_emmc_desc *descs; + dma_addr_t descs_dma_addr; struct meson_tuning_params tp; bool vqmmc_enabled; }; -struct sd_emmc_desc { - u32 cmd_cfg; - u32 cmd_arg; - u32 cmd_data; - u32 cmd_resp; -}; - #define CMD_CFG_LENGTH_MASK GENMASK(8, 0) #define CMD_CFG_BLOCK_MODE BIT(9) #define CMD_CFG_R1B BIT(10) @@ -195,6 +202,66 @@ static struct mmc_command *meson_mmc_get_next_command(struct mmc_command *cmd) return NULL; } +static void meson_mmc_get_transfer_mode(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + struct scatterlist *sg; + int i; + bool use_desc_chain_mode = true; + + for_each_sg(data->sg, sg, data->sg_len, i) + /* check for 8 byte alignment */ + if (sg->offset & 7) { + WARN_ONCE(1, "unaligned scatterlist buffer\n"); + use_desc_chain_mode = false; + break; + } + + if (use_desc_chain_mode) + data->host_cookie |= SD_EMMC_DESC_CHAIN_MODE; +} + +static inline bool meson_mmc_desc_chain_mode(const struct mmc_data *data) +{ + return data->host_cookie & SD_EMMC_DESC_CHAIN_MODE; +} + +static inline bool meson_mmc_bounce_buf_read(const struct mmc_data *data) +{ + return data && data->flags & MMC_DATA_READ && + !meson_mmc_desc_chain_mode(data); +} + +static void meson_mmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + + if (!data) + return; + + meson_mmc_get_transfer_mode(mmc, mrq); + data->host_cookie |= SD_EMMC_PRE_REQ_DONE; + + if (!meson_mmc_desc_chain_mode(data)) + return; + + data->sg_count = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); + if (!data->sg_count) + dev_err(mmc_dev(mmc), "dma_map_sg failed"); +} + +static void meson_mmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq, + int err) +{ + struct mmc_data *data = mrq->data; + + if (data && meson_mmc_desc_chain_mode(data) && data->sg_count) + dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); +} + static int meson_mmc_clk_set(struct meson_host *host, unsigned long clk_rate) { struct mmc_host *mmc = host->mmc; @@ -509,6 +576,44 @@ static void meson_mmc_set_response_bits(struct mmc_command *cmd, u32 *cmd_cfg) } } +static void meson_mmc_desc_chain_transfer(struct mmc_host *mmc, u32 cmd_cfg) +{ + struct meson_host *host = mmc_priv(mmc); + struct sd_emmc_desc *desc = host->descs; + struct mmc_data *data = host->cmd->data; + struct scatterlist *sg; + u32 start; + int i; + + if (data->flags & MMC_DATA_WRITE) + cmd_cfg |= CMD_CFG_DATA_WR; + + if (data->blocks > 1) { + cmd_cfg |= CMD_CFG_BLOCK_MODE; + meson_mmc_set_blksz(mmc, data->blksz); + } + + for_each_sg(data->sg, sg, data->sg_count, i) { + unsigned int len = sg_dma_len(sg); + + if (data->blocks > 1) + len /= data->blksz; + + desc[i].cmd_cfg = cmd_cfg; + desc[i].cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, len); + if (i > 0) + desc[i].cmd_cfg |= CMD_CFG_NO_CMD; + desc[i].cmd_arg = host->cmd->arg; + desc[i].cmd_resp = 0; + desc[i].cmd_data = sg_dma_address(sg); + } + desc[data->sg_count - 1].cmd_cfg |= CMD_CFG_END_OF_CHAIN; + + dma_wmb(); /* ensure descriptor is written before kicked */ + start = host->descs_dma_addr | START_DESC_BUSY; + writel(start, host->regs + SD_EMMC_START); +} + static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) { struct meson_host *host = mmc_priv(mmc); @@ -519,6 +624,8 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) /* Setup descriptors */ dma_rmb(); + host->cmd = cmd; + cmd_cfg |= FIELD_PREP(CMD_CFG_CMD_INDEX_MASK, cmd->opcode); cmd_cfg |= CMD_CFG_OWNER; /* owned by CPU */ @@ -526,10 +633,16 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) /* data? */ if (data) { + data->bytes_xfered = 0; cmd_cfg |= CMD_CFG_DATA_IO; cmd_cfg |= FIELD_PREP(CMD_CFG_TIMEOUT_MASK, ilog2(meson_mmc_get_timeout_msecs(data))); + if (meson_mmc_desc_chain_mode(data)) { + meson_mmc_desc_chain_transfer(mmc, cmd_cfg); + return; + } + if (data->blocks > 1) { cmd_cfg |= CMD_CFG_BLOCK_MODE; cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, @@ -539,7 +652,6 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, data->blksz); } - data->bytes_xfered = 0; xfer_bytes = data->blksz * data->blocks; if (data->flags & MMC_DATA_WRITE) { cmd_cfg |= CMD_CFG_DATA_WR; @@ -555,8 +667,6 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) ilog2(SD_EMMC_CMD_TIMEOUT)); } - host->cmd = cmd; - /* Last descriptor */ cmd_cfg |= CMD_CFG_END_OF_CHAIN; writel(cmd_cfg, host->regs + SD_EMMC_CMD_CFG); @@ -569,14 +679,25 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd) static void meson_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct meson_host *host = mmc_priv(mmc); + bool needs_pre_post_req = mrq->data && + !(mrq->data->host_cookie & SD_EMMC_PRE_REQ_DONE); + + if (needs_pre_post_req) { + meson_mmc_get_transfer_mode(mmc, mrq); + if (!meson_mmc_desc_chain_mode(mrq->data)) + needs_pre_post_req = false; + } + + if (needs_pre_post_req) + meson_mmc_pre_req(mmc, mrq); /* Stop execution */ writel(0, host->regs + SD_EMMC_START); - if (mrq->sbc) - meson_mmc_start_cmd(mmc, mrq->sbc); - else - meson_mmc_start_cmd(mmc, mrq->cmd); + meson_mmc_start_cmd(mmc, mrq->sbc ?: mrq->cmd); + + if (needs_pre_post_req) + meson_mmc_post_req(mmc, mrq, 0); } static void meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd) @@ -654,7 +775,9 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id) if (status & (IRQ_END_OF_CHAIN | IRQ_RESP_STATUS)) { if (data && !cmd->error) data->bytes_xfered = data->blksz * data->blocks; - ret = IRQ_WAKE_THREAD; + if (meson_mmc_bounce_buf_read(data) || + meson_mmc_get_next_command(cmd)) + ret = IRQ_WAKE_THREAD; } else { dev_warn(host->dev, "Unknown IRQ! status=0x%04x: MMC CMD%u arg=0x%08x flags=0x%08x stop=%d\n", status, cmd->opcode, cmd->arg, @@ -691,7 +814,7 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id) return IRQ_NONE; data = cmd->data; - if (data && data->flags & MMC_DATA_READ) { + if (meson_mmc_bounce_buf_read(data)) { xfer_bytes = data->blksz * data->blocks; WARN_ON(xfer_bytes > host->bounce_buf_size); sg_copy_from_buffer(data->sg, data->sg_len, @@ -760,6 +883,8 @@ static const struct mmc_host_ops meson_mmc_ops = { .request = meson_mmc_request, .set_ios = meson_mmc_set_ios, .get_cd = meson_mmc_get_cd, + .pre_req = meson_mmc_pre_req, + .post_req = meson_mmc_post_req, .execute_tuning = meson_mmc_execute_tuning, }; @@ -845,6 +970,8 @@ static int meson_mmc_probe(struct platform_device *pdev) mmc->caps |= MMC_CAP_CMD23; mmc->max_blk_count = CMD_CFG_LENGTH_MASK; mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size; + mmc->max_segs = SD_EMMC_DESC_BUF_LEN / sizeof(struct sd_emmc_desc); + mmc->max_seg_size = mmc->max_req_size; /* data bounce buffer */ host->bounce_buf_size = mmc->max_req_size; @@ -857,11 +984,22 @@ static int meson_mmc_probe(struct platform_device *pdev) goto err_div_clk; } + host->descs = dma_alloc_coherent(host->dev, SD_EMMC_DESC_BUF_LEN, + &host->descs_dma_addr, GFP_KERNEL); + if (!host->descs) { + dev_err(host->dev, "Allocating descriptor DMA buffer failed\n"); + ret = -ENOMEM; + goto err_bounce_buf; + } + mmc->ops = &meson_mmc_ops; mmc_add_host(mmc); return 0; +err_bounce_buf: + dma_free_coherent(host->dev, host->bounce_buf_size, + host->bounce_buf, host->bounce_dma_addr); err_div_clk: clk_disable_unprepare(host->cfg_div_clk); err_core_clk: @@ -880,6 +1018,8 @@ static int meson_mmc_remove(struct platform_device *pdev) /* disable interrupts */ writel(0, host->regs + SD_EMMC_IRQ_EN); + dma_free_coherent(host->dev, SD_EMMC_DESC_BUF_LEN, + host->descs, host->descs_dma_addr); dma_free_coherent(host->dev, host->bounce_buf_size, host->bounce_buf, host->bounce_dma_addr); From c34d1579de4665d9613691bc37aebb7ca86d535d Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 13 Apr 2017 14:10:50 +0200 Subject: [PATCH 127/148] mmc: cavium: Remove redundant pointer check Remove redundant mmc->card check reported by smatch: drivers/mmc/host/cavium.c:694 cvm_mmc_dma_request() warn: variable dereferenced before check 'mmc->card' (see line 675) Signed-off-by: Jan Glauber Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c index d842b6986189..ddf902c7f7e3 100644 --- a/drivers/mmc/host/cavium.c +++ b/drivers/mmc/host/cavium.c @@ -691,7 +691,7 @@ static void cvm_mmc_dma_request(struct mmc_host *mmc, * bit mask to check for CRC errors and timeouts only. * Otherwise, use the default power reset value. */ - if (mmc->card && mmc_card_sd(mmc->card)) + if (mmc_card_sd(mmc->card)) writeq(0x00b00000ull, host->base + MIO_EMM_STS_MASK(host)); else writeq(0xe4390080ull, host->base + MIO_EMM_STS_MASK(host)); From fe79018aa4c3af7d74e48b9a9b117da9fb6cf353 Mon Sep 17 00:00:00 2001 From: Jan Glauber Date: Thu, 13 Apr 2017 14:10:51 +0200 Subject: [PATCH 128/148] mmc: cavium: Check pointer before de-reference Add a pointer check to prevent this smatch warning: drivers/mmc/host/cavium.c:803 cvm_mmc_request() error: we previously assumed 'cmd->data' could be null (see line 782) This is a theoretical fix because MMC_CMD_ADTC seems to imply that cmd->data is not null. Nevertheless checking cmd->data before using it improves readability. Signed-off-by: Jan Glauber Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c index ddf902c7f7e3..d89c2ebcad90 100644 --- a/drivers/mmc/host/cavium.c +++ b/drivers/mmc/host/cavium.c @@ -799,7 +799,7 @@ static void cvm_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) FIELD_PREP(MIO_EMM_CMD_IDX, cmd->opcode) | FIELD_PREP(MIO_EMM_CMD_ARG, cmd->arg); set_bus_id(&emm_cmd, slot->bus_id); - if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) + if (cmd->data && mmc_cmd_type(cmd) == MMC_CMD_ADTC) emm_cmd |= FIELD_PREP(MIO_EMM_CMD_OFFSET, 64 - ((cmd->data->blocks * cmd->data->blksz) / 8)); From 3fcc78340f3a9bef2be7ad54258b06e1f177d3fd Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Apr 2017 22:47:48 +0300 Subject: [PATCH 129/148] mmc: cavium: Fix a shift wrapping bug "dat" is a u64 and "shift" starts as 54 so this is a shift wrapping bug. Signed-off-by: Dan Carpenter Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c index d89c2ebcad90..d89c011bbea8 100644 --- a/drivers/mmc/host/cavium.c +++ b/drivers/mmc/host/cavium.c @@ -733,7 +733,7 @@ static void do_write_request(struct cvm_mmc_host *host, struct mmc_request *mrq) } while (smi->consumed < smi->length && shift >= 0) { - dat |= ((u8 *)smi->addr)[smi->consumed] << shift; + dat |= (u64)((u8 *)smi->addr)[smi->consumed] << shift; bytes_xfered++; smi->consumed++; shift -= 8; From a72016a44703476bdb3567a13695938566e8f413 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Wed, 19 Apr 2017 15:48:55 +0300 Subject: [PATCH 130/148] mmc: sdhci-pci: Allow for 3 bytes from Intel DSM The DSM used by some Intel controllers can return a 3 byte package. Allow for that by using memcpy to copy the bytes. Signed-off-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pci-core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 833072b8453e..92fc3f7c538d 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -12,6 +12,7 @@ * - JMicron (hardware and technical support) */ +#include #include #include #include @@ -413,6 +414,7 @@ static int __intel_dsm(struct intel_host *intel_host, struct device *dev, { union acpi_object *obj; int err = 0; + size_t len; obj = acpi_evaluate_dsm(ACPI_HANDLE(dev), intel_dsm_uuid, 0, fn, NULL); if (!obj) @@ -423,12 +425,10 @@ static int __intel_dsm(struct intel_host *intel_host, struct device *dev, goto out; } - if (obj->buffer.length >= 4) - *result = *(u32 *)obj->buffer.pointer; - else if (obj->buffer.length >= 2) - *result = *(u16 *)obj->buffer.pointer; - else - *result = *(u8 *)obj->buffer.pointer; + len = min_t(size_t, obj->buffer.length, 4); + + *result = 0; + memcpy(result, obj->buffer.pointer, len); out: ACPI_FREE(obj); From 19c3a0ef65adf090cb592dfe3e575f40322c88d8 Mon Sep 17 00:00:00 2001 From: yangbo lu Date: Thu, 20 Apr 2017 16:14:40 +0800 Subject: [PATCH 131/148] mmc: sdhci-of-esdhc: add peripheral clock support eSDHC could select peripheral clock or platform clock as clock source by the PCS bit of eSDHC Control Register, and this bit couldn't be reset by software reset for all. In default, the platform clock is used. But we have to use peripheral clock since it has a higher frequency to support eMMC HS200 mode and SD UHS-I mode. This patch is to add peripheral clock support and use it instead of platform clock if it's declared in eSDHC dts node. Signed-off-by: Yangbo Lu Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc.h | 1 + drivers/mmc/host/sdhci-of-esdhc.c | 73 ++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h index ece8b37e51dd..5343fc062e22 100644 --- a/drivers/mmc/host/sdhci-esdhc.h +++ b/drivers/mmc/host/sdhci-esdhc.h @@ -54,6 +54,7 @@ /* Control Register for DMA transfer */ #define ESDHC_DMA_SYSCTL 0x40c +#define ESDHC_PERIPHERAL_CLK_SEL 0x00080000 #define ESDHC_DMA_SNOOP 0x00000040 #endif /* _DRIVERS_MMC_SDHCI_ESDHC_H */ diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index ff37e7459386..0b3f05ae8328 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include "sdhci-pltfm.h" #include "sdhci-esdhc.h" @@ -30,6 +32,7 @@ struct sdhci_esdhc { u8 vendor_ver; u8 spec_ver; bool quirk_incorrect_hostver; + unsigned int peripheral_clock; }; /** @@ -414,15 +417,25 @@ static int esdhc_of_enable_dma(struct sdhci_host *host) static unsigned int esdhc_of_get_max_clock(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host); - return pltfm_host->clock; + if (esdhc->peripheral_clock) + return esdhc->peripheral_clock; + else + return pltfm_host->clock; } static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host); + unsigned int clock; - return pltfm_host->clock / 256 / 16; + if (esdhc->peripheral_clock) + clock = esdhc->peripheral_clock; + else + clock = pltfm_host->clock; + return clock / 256 / 16; } static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) @@ -512,6 +525,33 @@ static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width) sdhci_writel(host, ctrl, ESDHC_PROCTL); } +static void esdhc_clock_enable(struct sdhci_host *host, bool enable) +{ + u32 val; + ktime_t timeout; + + val = sdhci_readl(host, ESDHC_SYSTEM_CONTROL); + + if (enable) + val |= ESDHC_CLOCK_SDCLKEN; + else + val &= ~ESDHC_CLOCK_SDCLKEN; + + sdhci_writel(host, val, ESDHC_SYSTEM_CONTROL); + + /* Wait max 20 ms */ + timeout = ktime_add_ms(ktime_get(), 20); + val = ESDHC_CLOCK_STABLE; + while (!(sdhci_readl(host, ESDHC_PRSSTAT) & val)) { + if (ktime_after(ktime_get(), timeout)) { + pr_err("%s: Internal clock never stabilised.\n", + mmc_hostname(host->mmc)); + break; + } + udelay(10); + } +} + static void esdhc_reset(struct sdhci_host *host, u8 mask) { sdhci_reset(host, mask); @@ -613,6 +653,9 @@ static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host; struct sdhci_esdhc *esdhc; + struct device_node *np; + struct clk *clk; + u32 val; u16 host_ver; pltfm_host = sdhci_priv(host); @@ -626,6 +669,32 @@ static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host) esdhc->quirk_incorrect_hostver = true; else esdhc->quirk_incorrect_hostver = false; + + np = pdev->dev.of_node; + clk = of_clk_get(np, 0); + if (!IS_ERR(clk)) { + /* + * esdhc->peripheral_clock would be assigned with a value + * which is eSDHC base clock when use periperal clock. + * For ls1046a, the clock value got by common clk API is + * peripheral clock while the eSDHC base clock is 1/2 + * peripheral clock. + */ + if (of_device_is_compatible(np, "fsl,ls1046a-esdhc")) + esdhc->peripheral_clock = clk_get_rate(clk) / 2; + else + esdhc->peripheral_clock = clk_get_rate(clk); + + clk_put(clk); + } + + if (esdhc->peripheral_clock) { + esdhc_clock_enable(host, false); + val = sdhci_readl(host, ESDHC_DMA_SYSCTL); + val |= ESDHC_PERIPHERAL_CLK_SEL; + sdhci_writel(host, val, ESDHC_DMA_SYSCTL); + esdhc_clock_enable(host, true); + } } static int sdhci_esdhc_probe(struct platform_device *pdev) From ea35645a3c66a74af92d3bbb4eb131220fc3e58a Mon Sep 17 00:00:00 2001 From: yangbo lu Date: Thu, 20 Apr 2017 16:14:41 +0800 Subject: [PATCH 132/148] mmc: sdhci-of-esdhc: add support for signal voltage switch eSDHC supports signal voltage switch from 3.3v to 1.8v by eSDHC_PROCTL[VOLT_SEL] bit. This bit changes the value of output signal SDHC_VS, and there must be a control circuit out of eSDHC to change the signal voltage according to SDHC_VS output signal. Signed-off-by: Yangbo Lu Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc.h | 1 + drivers/mmc/host/sdhci-of-esdhc.c | 74 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h index 5343fc062e22..68695679fed5 100644 --- a/drivers/mmc/host/sdhci-esdhc.h +++ b/drivers/mmc/host/sdhci-esdhc.h @@ -37,6 +37,7 @@ /* Protocol Control Register */ #define ESDHC_PROCTL 0x28 +#define ESDHC_VOLT_SEL 0x00000400 #define ESDHC_CTRL_4BITBUS (0x1 << 1) #define ESDHC_CTRL_8BITBUS (0x2 << 1) #define ESDHC_CTRL_BUSWIDTH_MASK (0x3 << 1) diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index 0b3f05ae8328..f2d7002e37c1 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -560,6 +561,76 @@ static void esdhc_reset(struct sdhci_host *host, u8 mask) sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); } +/* The SCFG, Supplemental Configuration Unit, provides SoC specific + * configuration and status registers for the device. There is a + * SDHC IO VSEL control register on SCFG for some platforms. It's + * used to support SDHC IO voltage switching. + */ +static const struct of_device_id scfg_device_ids[] = { + { .compatible = "fsl,t1040-scfg", }, + { .compatible = "fsl,ls1012a-scfg", }, + { .compatible = "fsl,ls1046a-scfg", }, + {} +}; + +/* SDHC IO VSEL control register definition */ +#define SCFG_SDHCIOVSELCR 0x408 +#define SDHCIOVSELCR_TGLEN 0x80000000 +#define SDHCIOVSELCR_VSELVAL 0x60000000 +#define SDHCIOVSELCR_SDHC_VS 0x00000001 + +static int esdhc_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct device_node *scfg_node; + void __iomem *scfg_base = NULL; + u32 sdhciovselcr; + u32 val; + + /* + * Signal Voltage Switching is only applicable for Host Controllers + * v3.00 and above. + */ + if (host->version < SDHCI_SPEC_300) + return 0; + + val = sdhci_readl(host, ESDHC_PROCTL); + + switch (ios->signal_voltage) { + case MMC_SIGNAL_VOLTAGE_330: + val &= ~ESDHC_VOLT_SEL; + sdhci_writel(host, val, ESDHC_PROCTL); + return 0; + case MMC_SIGNAL_VOLTAGE_180: + scfg_node = of_find_matching_node(NULL, scfg_device_ids); + if (scfg_node) + scfg_base = of_iomap(scfg_node, 0); + if (scfg_base) { + sdhciovselcr = SDHCIOVSELCR_TGLEN | + SDHCIOVSELCR_VSELVAL; + iowrite32be(sdhciovselcr, + scfg_base + SCFG_SDHCIOVSELCR); + + val |= ESDHC_VOLT_SEL; + sdhci_writel(host, val, ESDHC_PROCTL); + mdelay(5); + + sdhciovselcr = SDHCIOVSELCR_TGLEN | + SDHCIOVSELCR_SDHC_VS; + iowrite32be(sdhciovselcr, + scfg_base + SCFG_SDHCIOVSELCR); + iounmap(scfg_base); + } else { + val |= ESDHC_VOLT_SEL; + sdhci_writel(host, val, ESDHC_PROCTL); + } + return 0; + default: + return 0; + } +} + #ifdef CONFIG_PM_SLEEP static u32 esdhc_proctl; static int esdhc_of_suspend(struct device *dev) @@ -717,6 +788,9 @@ static int sdhci_esdhc_probe(struct platform_device *pdev) if (IS_ERR(host)) return PTR_ERR(host); + host->mmc_host_ops.start_signal_voltage_switch = + esdhc_signal_voltage_switch; + esdhc_init(pdev, host); sdhci_get_of_property(pdev); From ba49cbd0936e12eac245c0fb420a291e8505f639 Mon Sep 17 00:00:00 2001 From: yangbo lu Date: Thu, 20 Apr 2017 16:14:42 +0800 Subject: [PATCH 133/148] mmc: sdhci-of-esdhc: add tuning support eSDHC uses tuning block for tuning procedure. So the tuning block control register must be configured properly before tuning. Signed-off-by: Yangbo Lu Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc.h | 5 +++++ drivers/mmc/host/sdhci-of-esdhc.c | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h index 68695679fed5..c4bbd7485987 100644 --- a/drivers/mmc/host/sdhci-esdhc.h +++ b/drivers/mmc/host/sdhci-esdhc.h @@ -53,9 +53,14 @@ #define ESDHC_CLOCK_HCKEN 0x00000002 #define ESDHC_CLOCK_IPGEN 0x00000001 +/* Tuning Block Control Register */ +#define ESDHC_TBCTL 0x120 +#define ESDHC_TB_EN 0x00000004 + /* Control Register for DMA transfer */ #define ESDHC_DMA_SYSCTL 0x40c #define ESDHC_PERIPHERAL_CLK_SEL 0x00080000 +#define ESDHC_FLUSH_ASYNC_FIFO 0x00040000 #define ESDHC_DMA_SNOOP 0x00000040 #endif /* _DRIVERS_MMC_SDHCI_ESDHC_H */ diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index f2d7002e37c1..607e5122e1a1 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -631,6 +631,25 @@ static int esdhc_signal_voltage_switch(struct mmc_host *mmc, } } +static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct sdhci_host *host = mmc_priv(mmc); + u32 val; + + /* Use tuning block for tuning procedure */ + esdhc_clock_enable(host, false); + val = sdhci_readl(host, ESDHC_DMA_SYSCTL); + val |= ESDHC_FLUSH_ASYNC_FIFO; + sdhci_writel(host, val, ESDHC_DMA_SYSCTL); + + val = sdhci_readl(host, ESDHC_TBCTL); + val |= ESDHC_TB_EN; + sdhci_writel(host, val, ESDHC_TBCTL); + esdhc_clock_enable(host, true); + + return sdhci_execute_tuning(mmc, opcode); +} + #ifdef CONFIG_PM_SLEEP static u32 esdhc_proctl; static int esdhc_of_suspend(struct device *dev) @@ -790,6 +809,7 @@ static int sdhci_esdhc_probe(struct platform_device *pdev) host->mmc_host_ops.start_signal_voltage_switch = esdhc_signal_voltage_switch; + host->mmc_host_ops.execute_tuning = esdhc_execute_tuning; esdhc_init(pdev, host); From 83b600b8de0e83661a8f40091a7966fac052a21b Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Thu, 20 Apr 2017 16:14:43 +0800 Subject: [PATCH 134/148] mmc: sdhci: Control the delay between tuning commands The delay between tuning commands for SD cards is not part of the specification. A driver that needs it probably needs it for eMMC too, whereas most drivers would probably like to set it to 0. Make it a host member (host->tuning_delay) that defaults to the existing behaviour. Drivers can set it to zero to eliminate the delay, or set it to a positive value to always have a delay. Signed-off-by: Adrian Hunter Signed-off-by: Yangbo Lu Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci.c | 11 ++++++++--- drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 4bdad8959ec2..ecd0d4350e8a 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2108,9 +2108,9 @@ static void __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode) break; } - /* eMMC spec does not require a delay between tuning cycles */ - if (opcode == MMC_SEND_TUNING_BLOCK) - mdelay(1); + /* Spec does not require a delay between tuning cycles */ + if (host->tuning_delay > 0) + mdelay(host->tuning_delay); } pr_info("%s: Tuning failed, falling back to fixed sampling clock\n", @@ -2172,6 +2172,9 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) host->mmc->retune_period = tuning_count; + if (host->tuning_delay < 0) + host->tuning_delay = opcode == MMC_SEND_TUNING_BLOCK; + sdhci_start_tuning(host); __sdhci_execute_tuning(host, opcode); @@ -3114,6 +3117,8 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, host->cqe_ier = SDHCI_CQE_INT_MASK; host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK; + host->tuning_delay = -1; + return host; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 78437f82609e..0469fa191493 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -538,6 +538,8 @@ struct sdhci_host { #define SDHCI_TUNING_MODE_1 0 #define SDHCI_TUNING_MODE_2 1 #define SDHCI_TUNING_MODE_3 2 + /* Delay (ms) between tuning commands */ + int tuning_delay; unsigned long private[0] ____cacheline_aligned; }; From 6b236f37b0fa88fac93e6782fd25cbf9907e5048 Mon Sep 17 00:00:00 2001 From: yangbo lu Date: Thu, 20 Apr 2017 16:14:44 +0800 Subject: [PATCH 135/148] mmc: sdhci-of-esdhc: add delay between tuning cycles It's observed that eSDHC needed delay between tuning cycles for HS200 successful tuning. This patch is to set 1ms delay for that. Signed-off-by: Yangbo Lu Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-esdhc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index 607e5122e1a1..7518360c03d2 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -810,6 +810,7 @@ static int sdhci_esdhc_probe(struct platform_device *pdev) host->mmc_host_ops.start_signal_voltage_switch = esdhc_signal_voltage_switch; host->mmc_host_ops.execute_tuning = esdhc_execute_tuning; + host->tuning_delay = 1; esdhc_init(pdev, host); From a82d12b8d5a626e30d2f15627079abdaa2e77c05 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 11 Apr 2017 14:37:43 +0100 Subject: [PATCH 136/148] clk: apn806: fix spelling mistake: "mising" -> "missing" trivial fix to spelling mistake in dev_warn message Signed-off-by: Colin Ian King Acked-by: Stephen Boyd Signed-off-by: Ulf Hansson --- drivers/clk/mvebu/ap806-system-controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/mvebu/ap806-system-controller.c b/drivers/clk/mvebu/ap806-system-controller.c index 103fe18a3c29..8155baccc98e 100644 --- a/drivers/clk/mvebu/ap806-system-controller.c +++ b/drivers/clk/mvebu/ap806-system-controller.c @@ -140,7 +140,7 @@ static int ap806_syscon_clk_probe(struct platform_device *pdev) 4, &name)) { ap806_clk_data.clk_num--; dev_warn(&pdev->dev, - "eMMC clock mising: update the device tree!\n"); + "eMMC clock missing: update the device tree!\n"); } else { ap806_clks[4] = clk_register_fixed_factor(NULL, name, fixedclk_name, From d9370424c9482d0ca2722627309c6f6bae131d97 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Tue, 18 Apr 2017 18:05:31 +0800 Subject: [PATCH 137/148] mmc: sdhci-esdhc-imx: reset tuning circuit when power on mmc card USDHC tuning circuit should be reset before every time card enumeration or re-enumeration. SD3.0 card need tuning. For SDR104 card, when system suspend in standby mode, and then resume back, the IO timing is still SDR104(tuned) which may result in card re-enumeration fail in low card speed(400khz) for some cards. And we did meet the issue that in certain probability, SDR104 card meet mmc command CRC/Timeout error when send CMD2 during mmc bus resume. This patch reset the tuning circuit when the ios timing is MMC_TIMING_LEGACY/MMC_TIMING_MMC_HS/MMC_TIMING_SD_HS, which means both mmc_power_up() and mmc_power_off() will reset the tuning circuit. Signed-off-by: Haibo Chen Acked-by: Dong Aisheng Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index abad67aeabdd..23d8b8a73ae9 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -889,6 +889,28 @@ static void esdhc_set_strobe_dll(struct sdhci_host *host) } } +static void esdhc_reset_tuning(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host); + u32 ctrl; + + /* Rest the tuning circurt */ + if (esdhc_is_usdhc(imx_data)) { + if (imx_data->socdata->flags & ESDHC_FLAG_MAN_TUNING) { + ctrl = readl(host->ioaddr + ESDHC_MIX_CTRL); + ctrl &= ~ESDHC_MIX_CTRL_SMPCLK_SEL; + ctrl &= ~ESDHC_MIX_CTRL_FBCLK_SEL; + writel(ctrl, host->ioaddr + ESDHC_MIX_CTRL); + writel(0, host->ioaddr + ESDHC_TUNE_CTRL_STATUS); + } else if (imx_data->socdata->flags & ESDHC_FLAG_STD_TUNING) { + ctrl = readl(host->ioaddr + SDHCI_ACMD12_ERR); + ctrl &= ~ESDHC_MIX_CTRL_SMPCLK_SEL; + writel(ctrl, host->ioaddr + SDHCI_ACMD12_ERR); + } + } +} + static void esdhc_set_uhs_signaling(struct sdhci_host *host, unsigned timing) { u32 m; @@ -932,6 +954,10 @@ static void esdhc_set_uhs_signaling(struct sdhci_host *host, unsigned timing) host->ops->set_clock(host, host->clock); esdhc_set_strobe_dll(host); break; + case MMC_TIMING_LEGACY: + default: + esdhc_reset_tuning(host); + break; } esdhc_change_pinstate(host, timing); From f328c76e568d13c9fee21b5cf7411c96285f0f89 Mon Sep 17 00:00:00 2001 From: "jiajie.hao@mediatek.com" Date: Sun, 23 Apr 2017 17:38:27 +0800 Subject: [PATCH 138/148] mmc: sdio: Fix sdio wait busy implement limitation The host may issue an I/O abort by writing to the CCCR at any time during I/O read operation via CMD52. And host may need suspend transcation during write busy stage in SDIO suspend/resume scenario. >From other side, a card may accept CMD52 during data transfer phase. Previous implement would block issuing above command in busy stage. It cause function driver can't implement as proper way and has no opportunity to do some coverage in error case via I/O abort etc. We need bypass some necessary operation during busy check stage. Signed-off-by: Jiajie Hao Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 4 +++- drivers/mmc/core/sdio_ops.h | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0bb39795d484..e5f6bbfa73dd 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -239,8 +239,10 @@ static void __mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) /* * For sdio rw commands we must wait for card busy otherwise some * sdio devices won't work properly. + * And bypass I/O abort, reset and bus suspend operations. */ - if (mmc_is_io_op(mrq->cmd->opcode) && host->ops->card_busy) { + if (sdio_is_io_busy(mrq->cmd->opcode, mrq->cmd->arg) && + host->ops->card_busy) { int tries = 500; /* Wait aprox 500ms at maximum */ while (host->ops->card_busy(host) && --tries) diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h index bed8a8377fec..ee35cb4d170e 100644 --- a/drivers/mmc/core/sdio_ops.h +++ b/drivers/mmc/core/sdio_ops.h @@ -26,9 +26,15 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, int sdio_reset(struct mmc_host *host); unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz); -static inline bool mmc_is_io_op(u32 opcode) +static inline bool sdio_is_io_busy(u32 opcode, u32 arg) { - return opcode == SD_IO_RW_DIRECT || opcode == SD_IO_RW_EXTENDED; + u32 addr; + + addr = (arg >> 9) & 0x1FFFF; + + return (opcode == SD_IO_RW_EXTENDED || + (opcode == SD_IO_RW_DIRECT && + !(addr == SDIO_CCCR_ABORT || addr == SDIO_CCCR_SUSPEND))); } #endif From 33e6d74d65c358270f00d228877178964aab84b3 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 24 Apr 2017 13:41:55 -0500 Subject: [PATCH 139/148] mmc: core: Export API to allow hosts to get the card address Some hosts controllers, like Cavium, needs to know whether the card operates in byte- or block-address mode. Therefore export a new API, mmc_card_is_blockaddr(), which provides this information. Signed-off-by: Ulf Hansson Signed-off-by: Steven J. Hill Acked-by: David Daney --- drivers/mmc/core/core.c | 6 ++++++ include/linux/mmc/card.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index e5f6bbfa73dd..82c45ddfa202 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2560,6 +2560,12 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card) } EXPORT_SYMBOL(mmc_calc_max_discard); +bool mmc_card_is_blockaddr(struct mmc_card *card) +{ + return card ? mmc_card_blockaddr(card) : false; +} +EXPORT_SYMBOL(mmc_card_is_blockaddr); + int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen) { struct mmc_command cmd = {}; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 85b5f2bc8bb9..aad015e0152b 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -315,6 +315,8 @@ static inline bool mmc_large_sector(struct mmc_card *card) return card->ext_csd.data_sector_size == 4096; } +bool mmc_card_is_blockaddr(struct mmc_card *card); + #define mmc_card_mmc(c) ((c)->type == MMC_TYPE_MMC) #define mmc_card_sd(c) ((c)->type == MMC_TYPE_SD) #define mmc_card_sdio(c) ((c)->type == MMC_TYPE_SDIO) From dceb9781dcad8b9bdb88b9f7c06b2e47f5ad789e Mon Sep 17 00:00:00 2001 From: "Steven J. Hill" Date: Mon, 24 Apr 2017 13:41:56 -0500 Subject: [PATCH 140/148] mmc: cavium: Fix detection of block or byte addressing. Use the mmc_card_is_blockaddr() function to properly detect if the card uses byte or block addressing. Signed-off-by: Steven J. Hill Acked-by: David Daney Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/cavium.c b/drivers/mmc/host/cavium.c index d89c011bbea8..58b51ba6aabd 100644 --- a/drivers/mmc/host/cavium.c +++ b/drivers/mmc/host/cavium.c @@ -629,7 +629,7 @@ static u64 prepare_ext_dma(struct mmc_host *mmc, struct mmc_request *mrq) emm_dma = FIELD_PREP(MIO_EMM_DMA_VAL, 1) | FIELD_PREP(MIO_EMM_DMA_SECTOR, - (mrq->data->blksz == 512) ? 1 : 0) | + mmc_card_is_blockaddr(mmc->card) ? 1 : 0) | FIELD_PREP(MIO_EMM_DMA_RW, (mrq->data->flags & MMC_DATA_WRITE) ? 1 : 0) | FIELD_PREP(MIO_EMM_DMA_BLOCK_CNT, mrq->data->blocks) | From 01d95843335c6d7bb7d100c0083165f489c6e50b Mon Sep 17 00:00:00 2001 From: "Steven J. Hill" Date: Mon, 24 Apr 2017 13:41:57 -0500 Subject: [PATCH 141/148] mmc: cavium: Add MMC support for Octeon SOCs. Add platform driver for Octeon SOCs. Signed-off-by: Steven J. Hill Signed-off-by: David Daney Signed-off-by: Ulf Hansson --- drivers/mmc/host/Kconfig | 10 + drivers/mmc/host/Makefile | 2 + drivers/mmc/host/cavium-octeon.c | 351 +++++++++++++++++++++++++++++++ 3 files changed, 363 insertions(+) create mode 100644 drivers/mmc/host/cavium-octeon.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 9cee339f337c..2db84dd664d7 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -622,6 +622,16 @@ config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND help If you say yes here SD-Cards may work on the EZkit. +config MMC_CAVIUM_OCTEON + tristate "Cavium OCTEON SD/MMC Card Interface support" + depends on CAVIUM_OCTEON_SOC + help + This selects Cavium OCTEON SD/MMC card Interface. + If you have an OCTEON board with a Multimedia Card slot, + say Y or M here. + + If unsure, say N. + config MMC_CAVIUM_THUNDERX tristate "Cavium ThunderX SD/MMC Card Interface support" depends on PCI && 64BIT && (ARM64 || COMPILE_TEST) diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 322df50a801d..926347c2eeb4 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -42,6 +42,8 @@ obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o +octeon-mmc-objs := cavium.o cavium-octeon.o +obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc.o thunderx-mmc-objs := cavium.o cavium-thunderx.o obj-$(CONFIG_MMC_CAVIUM_THUNDERX) += thunderx-mmc.o obj-$(CONFIG_MMC_DW) += dw_mmc.o diff --git a/drivers/mmc/host/cavium-octeon.c b/drivers/mmc/host/cavium-octeon.c new file mode 100644 index 000000000000..772d0900026d --- /dev/null +++ b/drivers/mmc/host/cavium-octeon.c @@ -0,0 +1,351 @@ +/* + * Driver for MMC and SSD cards for Cavium OCTEON SOCs. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2012-2017 Cavium Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "cavium.h" + +#define CVMX_MIO_BOOT_CTL CVMX_ADD_IO_SEG(0x00011800000000D0ull) + +/* + * The l2c* functions below are used for the EMMC-17978 workaround. + * + * Due to a bug in the design of the MMC bus hardware, the 2nd to last + * cache block of a DMA read must be locked into the L2 Cache. + * Otherwise, data corruption may occur. + */ +static inline void *phys_to_ptr(u64 address) +{ + return (void *)(address | (1ull << 63)); /* XKPHYS */ +} + +/* + * Lock a single line into L2. The line is zeroed before locking + * to make sure no dram accesses are made. + */ +static void l2c_lock_line(u64 addr) +{ + char *addr_ptr = phys_to_ptr(addr); + + asm volatile ( + "cache 31, %[line]" /* Unlock the line */ + ::[line] "m" (*addr_ptr)); +} + +/* Unlock a single line in the L2 cache. */ +static void l2c_unlock_line(u64 addr) +{ + char *addr_ptr = phys_to_ptr(addr); + + asm volatile ( + "cache 23, %[line]" /* Unlock the line */ + ::[line] "m" (*addr_ptr)); +} + +/* Locks a memory region in the L2 cache. */ +static void l2c_lock_mem_region(u64 start, u64 len) +{ + u64 end; + + /* Round start/end to cache line boundaries */ + end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE); + start = ALIGN(start, CVMX_CACHE_LINE_SIZE); + + while (start <= end) { + l2c_lock_line(start); + start += CVMX_CACHE_LINE_SIZE; + } + asm volatile("sync"); +} + +/* Unlock a memory region in the L2 cache. */ +static void l2c_unlock_mem_region(u64 start, u64 len) +{ + u64 end; + + /* Round start/end to cache line boundaries */ + end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE); + start = ALIGN(start, CVMX_CACHE_LINE_SIZE); + + while (start <= end) { + l2c_unlock_line(start); + start += CVMX_CACHE_LINE_SIZE; + } +} + +static void octeon_mmc_acquire_bus(struct cvm_mmc_host *host) +{ + if (!host->has_ciu3) { + down(&octeon_bootbus_sem); + /* For CN70XX, switch the MMC controller onto the bus. */ + if (OCTEON_IS_MODEL(OCTEON_CN70XX)) + writeq(0, (void __iomem *)CVMX_MIO_BOOT_CTL); + } else { + down(&host->mmc_serializer); + } +} + +static void octeon_mmc_release_bus(struct cvm_mmc_host *host) +{ + if (!host->has_ciu3) + up(&octeon_bootbus_sem); + else + up(&host->mmc_serializer); +} + +static void octeon_mmc_int_enable(struct cvm_mmc_host *host, u64 val) +{ + writeq(val, host->base + MIO_EMM_INT(host)); + if (!host->dma_active || (host->dma_active && !host->has_ciu3)) + writeq(val, host->base + MIO_EMM_INT_EN(host)); +} + +static void octeon_mmc_set_shared_power(struct cvm_mmc_host *host, int dir) +{ + if (dir == 0) + if (!atomic_dec_return(&host->shared_power_users)) + gpiod_set_value_cansleep(host->global_pwr_gpiod, 0); + if (dir == 1) + if (atomic_inc_return(&host->shared_power_users) == 1) + gpiod_set_value_cansleep(host->global_pwr_gpiod, 1); +} + +static void octeon_mmc_dmar_fixup(struct cvm_mmc_host *host, + struct mmc_command *cmd, + struct mmc_data *data, + u64 addr) +{ + if (cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) + return; + if (data->blksz * data->blocks <= 1024) + return; + + host->n_minus_one = addr + (data->blksz * data->blocks) - 1024; + l2c_lock_mem_region(host->n_minus_one, 512); +} + +static void octeon_mmc_dmar_fixup_done(struct cvm_mmc_host *host) +{ + if (!host->n_minus_one) + return; + l2c_unlock_mem_region(host->n_minus_one, 512); + host->n_minus_one = 0; +} + +static int octeon_mmc_probe(struct platform_device *pdev) +{ + struct device_node *cn, *node = pdev->dev.of_node; + struct cvm_mmc_host *host; + struct resource *res; + void __iomem *base; + int mmc_irq[9]; + int i, ret = 0; + u64 val; + + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + spin_lock_init(&host->irq_handler_lock); + sema_init(&host->mmc_serializer, 1); + + host->dev = &pdev->dev; + host->acquire_bus = octeon_mmc_acquire_bus; + host->release_bus = octeon_mmc_release_bus; + host->int_enable = octeon_mmc_int_enable; + host->set_shared_power = octeon_mmc_set_shared_power; + if (OCTEON_IS_MODEL(OCTEON_CN6XXX) || + OCTEON_IS_MODEL(OCTEON_CNF7XXX)) { + host->dmar_fixup = octeon_mmc_dmar_fixup; + host->dmar_fixup_done = octeon_mmc_dmar_fixup_done; + } + + host->sys_freq = octeon_get_io_clock_rate(); + + if (of_device_is_compatible(node, "cavium,octeon-7890-mmc")) { + host->big_dma_addr = true; + host->need_irq_handler_lock = true; + host->has_ciu3 = true; + host->use_sg = true; + /* + * First seven are the EMM_INT bits 0..6, then two for + * the EMM_DMA_INT bits + */ + for (i = 0; i < 9; i++) { + mmc_irq[i] = platform_get_irq(pdev, i); + if (mmc_irq[i] < 0) + return mmc_irq[i]; + + /* work around legacy u-boot device trees */ + irq_set_irq_type(mmc_irq[i], IRQ_TYPE_EDGE_RISING); + } + } else { + host->big_dma_addr = false; + host->need_irq_handler_lock = false; + host->has_ciu3 = false; + /* First one is EMM second DMA */ + for (i = 0; i < 2; i++) { + mmc_irq[i] = platform_get_irq(pdev, i); + if (mmc_irq[i] < 0) + return mmc_irq[i]; + } + } + + host->last_slot = -1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Platform resource[0] is missing\n"); + return -ENXIO; + } + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + host->base = (void __iomem *)base; + host->reg_off = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "Platform resource[1] is missing\n"); + return -EINVAL; + } + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + host->dma_base = (void __iomem *)base; + /* + * To keep the register addresses shared we intentionaly use + * a negative offset here, first register used on Octeon therefore + * starts at 0x20 (MIO_EMM_DMA_CFG). + */ + host->reg_off_dma = -0x20; + + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + /* + * Clear out any pending interrupts that may be left over from + * bootloader. + */ + val = readq(host->base + MIO_EMM_INT(host)); + writeq(val, host->base + MIO_EMM_INT(host)); + + if (host->has_ciu3) { + /* Only CMD_DONE, DMA_DONE, CMD_ERR, DMA_ERR */ + for (i = 1; i <= 4; i++) { + ret = devm_request_irq(&pdev->dev, mmc_irq[i], + cvm_mmc_interrupt, + 0, cvm_mmc_irq_names[i], host); + if (ret < 0) { + dev_err(&pdev->dev, "Error: devm_request_irq %d\n", + mmc_irq[i]); + return ret; + } + } + } else { + ret = devm_request_irq(&pdev->dev, mmc_irq[0], + cvm_mmc_interrupt, 0, KBUILD_MODNAME, + host); + if (ret < 0) { + dev_err(&pdev->dev, "Error: devm_request_irq %d\n", + mmc_irq[0]); + return ret; + } + } + + host->global_pwr_gpiod = devm_gpiod_get_optional(&pdev->dev, + "power-gpios", + GPIOD_OUT_HIGH); + if (IS_ERR(host->global_pwr_gpiod)) { + dev_err(&pdev->dev, "Invalid power GPIO\n"); + return PTR_ERR(host->global_pwr_gpiod); + } + + platform_set_drvdata(pdev, host); + + i = 0; + for_each_child_of_node(node, cn) { + host->slot_pdev[i] = + of_platform_device_create(cn, NULL, &pdev->dev); + if (!host->slot_pdev[i]) { + i++; + continue; + } + ret = cvm_mmc_of_slot_probe(&host->slot_pdev[i]->dev, host); + if (ret) { + dev_err(&pdev->dev, "Error populating slots\n"); + octeon_mmc_set_shared_power(host, 0); + return ret; + } + i++; + } + return 0; +} + +static int octeon_mmc_remove(struct platform_device *pdev) +{ + struct cvm_mmc_host *host = platform_get_drvdata(pdev); + u64 dma_cfg; + int i; + + for (i = 0; i < CAVIUM_MAX_MMC; i++) + if (host->slot[i]) + cvm_mmc_of_slot_remove(host->slot[i]); + + dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); + dma_cfg &= ~MIO_EMM_DMA_CFG_EN; + writeq(dma_cfg, host->dma_base + MIO_EMM_DMA_CFG(host)); + + octeon_mmc_set_shared_power(host, 0); + return 0; +} + +static const struct of_device_id octeon_mmc_match[] = { + { + .compatible = "cavium,octeon-6130-mmc", + }, + { + .compatible = "cavium,octeon-7890-mmc", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, octeon_mmc_match); + +static struct platform_driver octeon_mmc_driver = { + .probe = octeon_mmc_probe, + .remove = octeon_mmc_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = octeon_mmc_match, + }, +}; + +static int __init octeon_mmc_init(void) +{ + return platform_driver_register(&octeon_mmc_driver); +} + +static void __exit octeon_mmc_cleanup(void) +{ + platform_driver_unregister(&octeon_mmc_driver); +} + +module_init(octeon_mmc_init); +module_exit(octeon_mmc_cleanup); + +MODULE_AUTHOR("Cavium Inc. "); +MODULE_DESCRIPTION("Low-level driver for Cavium OCTEON MMC/SSD card"); +MODULE_LICENSE("GPL"); From dfc28b110d4f42953147204bc627b3c64205ed19 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 25 Apr 2017 03:14:09 +0000 Subject: [PATCH 142/148] mmc: cavium: Use module_pci_driver to simplify the code Use the module_pci_driver() macro to make the code simpler by eliminating module_init and module_exit calls. Signed-off-by: Wei Yongjun Acked-by: Jan Glauber Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium-thunderx.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/mmc/host/cavium-thunderx.c b/drivers/mmc/host/cavium-thunderx.c index 65244e8df5dd..fe3d77267cd6 100644 --- a/drivers/mmc/host/cavium-thunderx.c +++ b/drivers/mmc/host/cavium-thunderx.c @@ -179,18 +179,7 @@ static struct pci_driver thunder_mmc_driver = { .remove = thunder_mmc_remove, }; -static int __init thunder_mmc_init_module(void) -{ - return pci_register_driver(&thunder_mmc_driver); -} - -static void __exit thunder_mmc_exit_module(void) -{ - pci_unregister_driver(&thunder_mmc_driver); -} - -module_init(thunder_mmc_init_module); -module_exit(thunder_mmc_exit_module); +module_pci_driver(thunder_mmc_driver); MODULE_AUTHOR("Cavium Inc."); MODULE_DESCRIPTION("Cavium ThunderX eMMC Driver"); From e6e267b08fb5fe02311b54498a2545b7aad9f1bb Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 25 Apr 2017 06:34:46 +0000 Subject: [PATCH 143/148] mmc: sdhci-xenon: Remove redundant dev_err call in get_dt_pad_ctrl_data() There is a error message within devm_ioremap_resource already, so remove the dev_err call to avoid redundant error message. Signed-off-by: Wei Yongjun Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-xenon-phy.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c index 4bdbcd3f2645..d43841e9db14 100644 --- a/drivers/mmc/host/sdhci-xenon-phy.c +++ b/drivers/mmc/host/sdhci-xenon-phy.c @@ -639,11 +639,8 @@ static int get_dt_pad_ctrl_data(struct sdhci_host *host, params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc), &iomem); - if (IS_ERR(params->pad_ctrl.reg)) { - dev_err(mmc_dev(host->mmc), "Unable to get SoC PHY PAD ctrl register for %s\n", - np->name); + if (IS_ERR(params->pad_ctrl.reg)) return PTR_ERR(params->pad_ctrl.reg); - } ret = of_property_read_string(np, "marvell,pad-type", &name); if (ret) { From ff4143ccff314b22f03666c1d8eccd4de967df2c Mon Sep 17 00:00:00 2001 From: "Steven J. Hill" Date: Mon, 24 Apr 2017 13:41:58 -0500 Subject: [PATCH 144/148] MIPS: Octeon: cavium_octeon_defconfig: Enable Octeon MMC Enable the Octeon MMC driver in the defconfig. Signed-off-by: Steven J. Hill Acked-by: Ralf Baechle Signed-off-by: Ulf Hansson --- arch/mips/configs/cavium_octeon_defconfig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/mips/configs/cavium_octeon_defconfig b/arch/mips/configs/cavium_octeon_defconfig index 31e3c4d9adb0..d4fda41f00ba 100644 --- a/arch/mips/configs/cavium_octeon_defconfig +++ b/arch/mips/configs/cavium_octeon_defconfig @@ -127,6 +127,11 @@ CONFIG_USB_EHCI_HCD=m CONFIG_USB_EHCI_HCD_PLATFORM=m CONFIG_USB_OHCI_HCD=m CONFIG_USB_OHCI_HCD_PLATFORM=m +CONFIG_MMC=y +# CONFIG_PWRSEQ_EMMC is not set +# CONFIG_PWRSEQ_SIMPLE is not set +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_CAVIUM_OCTEON=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_DS1307=y CONFIG_STAGING=y From 4cc59dffa9cb579613776cad5b777236210496d8 Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Fri, 28 Apr 2017 10:34:59 +0800 Subject: [PATCH 145/148] mmc: sdhci-xenon: Fix the work flow in xenon_remove(). sdhci_remove_host() might execute SOFT_RESET_ALL. Inside xenon_remove(), Xenon SDHC should be enabled during sdhci_remove_host(). Move xenon_sdhc_unprepare after sdhci_remove_host() such that Xenon SDHC is disabled after sdhci_remove_host() completes. Signed-off-by: Hu Ziji Reported-by: Jisheng Zhang Tested-by: Jisheng Zhang Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-xenon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c index 8e56b9ccfb39..67246655315b 100644 --- a/drivers/mmc/host/sdhci-xenon.c +++ b/drivers/mmc/host/sdhci-xenon.c @@ -512,10 +512,10 @@ static int xenon_remove(struct platform_device *pdev) xenon_clean_phy(host); - xenon_sdhc_unprepare(host); - sdhci_remove_host(host, 0); + xenon_sdhc_unprepare(host); + clk_disable_unprepare(pltfm_host->clk); sdhci_pltfm_free(pdev); From a04b9b47ae587e088439495f31ca740d92191a75 Mon Sep 17 00:00:00 2001 From: Hu Ziji Date: Fri, 28 Apr 2017 10:35:15 +0800 Subject: [PATCH 146/148] mmc: sdhci-xenon: Fix default value of LOGIC_TIMING_ADJUST for eMMC5.0 PHY The default value of LOGIC_TIMING_ADJUST register in eMMC 5.0 PHY is different from that in eMMC 5.1 PHY. Set the specific value for that register in eMMC 5.0 PHY. Signed-off-by: Hu Ziji Reported-by: Jisheng Zhang Tested-by: Jisheng Zhang Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-xenon-phy.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c index d43841e9db14..6356781f1cca 100644 --- a/drivers/mmc/host/sdhci-xenon-phy.c +++ b/drivers/mmc/host/sdhci-xenon-phy.c @@ -108,6 +108,7 @@ #define XENON_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST \ (XENON_EMMC_5_0_PHY_REG_BASE + 0x14) +#define XENON_EMMC_5_0_PHY_LOGIC_TIMING_VALUE 0x5A54 #define XENON_EMMC_PHY_LOGIC_TIMING_ADJUST (XENON_EMMC_PHY_REG_BASE + 0x18) #define XENON_LOGIC_TIMING_VALUE 0x00AA8977 @@ -130,6 +131,8 @@ struct xenon_emmc_phy_regs { u16 logic_timing_adj; /* DLL Update Enable bit */ u32 dll_update; + /* value in Logic Timing Adjustment register */ + u32 logic_timing_val; }; static const char * const phy_types[] = { @@ -166,6 +169,7 @@ static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = { .dll_ctrl = XENON_EMMC_5_0_PHY_DLL_CONTROL, .logic_timing_adj = XENON_EMMC_5_0_PHY_LOGIC_TIMING_ADJUST, .dll_update = XENON_DLL_UPDATE_STROBE_5_0, + .logic_timing_val = XENON_EMMC_5_0_PHY_LOGIC_TIMING_VALUE, }; static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = { @@ -176,6 +180,7 @@ static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = { .dll_ctrl = XENON_EMMC_PHY_DLL_CONTROL, .logic_timing_adj = XENON_EMMC_PHY_LOGIC_TIMING_ADJUST, .dll_update = XENON_DLL_UPDATE, + .logic_timing_val = XENON_LOGIC_TIMING_VALUE, }; /* @@ -607,7 +612,7 @@ static void xenon_emmc_phy_set(struct sdhci_host *host, if (timing == MMC_TIMING_MMC_HS400) /* Hardware team recommend a value for HS400 */ - sdhci_writel(host, XENON_LOGIC_TIMING_VALUE, + sdhci_writel(host, phy_regs->logic_timing_val, phy_regs->logic_timing_adj); else xenon_emmc_phy_disable_data_strobe(host); From e145ac451eb68b51e0ede4c131bd5a539fb675b6 Mon Sep 17 00:00:00 2001 From: yangbo lu Date: Wed, 26 Apr 2017 10:45:49 +0800 Subject: [PATCH 147/148] mmc: sdhci-of-esdhc: poll ESDHC_CLOCK_STABLE bit with udelay The loop to poll ESDHC_CLOCK_STABLE bit with mdelay would waste time because the time to stabilize is much less than 1 ms. This patch is to use udelay instead to avoid time wasting. Signed-off-by: Yangbo Lu Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-esdhc.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index 7518360c03d2..77dfc5915ddc 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -445,7 +445,7 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host); int pre_div = 1; int div = 1; - u32 timeout; + ktime_t timeout; u32 temp; host->mmc->actual_clock = 0; @@ -489,15 +489,14 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); /* Wait max 20 ms */ - timeout = 20; + timeout = ktime_add_ms(ktime_get(), 20); while (!(sdhci_readl(host, ESDHC_PRSSTAT) & ESDHC_CLOCK_STABLE)) { - if (timeout == 0) { + if (ktime_after(ktime_get(), timeout)) { pr_err("%s: Internal clock never stabilised.\n", mmc_hostname(host->mmc)); return; } - timeout--; - mdelay(1); + udelay(10); } temp |= ESDHC_CLOCK_SDCLKEN; From a627f025eb0534052ff451427c16750b3530634c Mon Sep 17 00:00:00 2001 From: yangbo lu Date: Thu, 20 Apr 2017 14:58:29 +0800 Subject: [PATCH 148/148] mmc: sdhci-of-esdhc: limit SD clock for ls1012a/ls1046a The ls1046a datasheet specified that the max SD clock frequency for eSDHC SDR104/HS200 was 167MHz, and the ls1012a datasheet specified it's 125MHz for ls1012a. So this patch is to add the limitation. Signed-off-by: Yangbo Lu Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-esdhc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index 77dfc5915ddc..44b016baa585 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -457,6 +457,20 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) if (esdhc->vendor_ver < VENDOR_V_23) pre_div = 2; + /* + * Limit SD clock to 167MHz for ls1046a according to its datasheet + */ + if (clock > 167000000 && + of_find_compatible_node(NULL, NULL, "fsl,ls1046a-esdhc")) + clock = 167000000; + + /* + * Limit SD clock to 125MHz for ls1012a according to its datasheet + */ + if (clock > 125000000 && + of_find_compatible_node(NULL, NULL, "fsl,ls1012a-esdhc")) + clock = 125000000; + /* Workaround to reduce the clock frequency for p1010 esdhc */ if (of_find_compatible_node(NULL, NULL, "fsl,p1010-esdhc")) { if (clock > 20000000)