-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This patch adds a stm32 sdmmc variant, rev 1.1. Introduces a new Manufacturer id "0x53, ascii 'S' to define new stm32 sdmmc family with clean range of amba revision/configurations bits (corresponding to sdmmc_ver register with major/minor fields). Add 2 variants properties: -dma_lli, to enable link list support. -stm32_idmabsize_mask, defines the range of SDMMC_IDMABSIZER register which specify the number bytes per buffer. DT properties for sdmmc: -Indicate signal directions (only one property for d0dir, d123dir, cmd_dir) -Select command and data phase relation. -Select "clock in" from an external driver. Signed-off-by: Ludovic Barre <ludovic.barre@st.com> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
- Loading branch information
1 parent
6202289
commit 46b723d
Showing
5 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Copyright (C) STMicroelectronics 2018 - All Rights Reserved | ||
* Author: Ludovic.barre@st.com for STMicroelectronics. | ||
*/ | ||
#include <linux/delay.h> | ||
#include <linux/dma-mapping.h> | ||
#include <linux/mmc/host.h> | ||
#include <linux/mmc/card.h> | ||
#include <linux/reset.h> | ||
#include <linux/scatterlist.h> | ||
#include "mmci.h" | ||
|
||
#define SDMMC_LLI_BUF_LEN PAGE_SIZE | ||
#define SDMMC_IDMA_BURST BIT(MMCI_STM32_IDMABNDT_SHIFT) | ||
|
||
struct sdmmc_lli_desc { | ||
u32 idmalar; | ||
u32 idmabase; | ||
u32 idmasize; | ||
}; | ||
|
||
struct sdmmc_priv { | ||
dma_addr_t sg_dma; | ||
void *sg_cpu; | ||
}; | ||
|
||
int sdmmc_idma_validate_data(struct mmci_host *host, | ||
struct mmc_data *data) | ||
{ | ||
struct scatterlist *sg; | ||
int i; | ||
|
||
/* | ||
* idma has constraints on idmabase & idmasize for each element | ||
* excepted the last element which has no constraint on idmasize | ||
*/ | ||
for_each_sg(data->sg, sg, data->sg_len - 1, i) { | ||
if (!IS_ALIGNED(sg_dma_address(data->sg), sizeof(u32)) || | ||
!IS_ALIGNED(sg_dma_len(data->sg), SDMMC_IDMA_BURST)) { | ||
dev_err(mmc_dev(host->mmc), | ||
"unaligned scatterlist: ofst:%x length:%d\n", | ||
data->sg->offset, data->sg->length); | ||
return -EINVAL; | ||
} | ||
} | ||
|
||
if (!IS_ALIGNED(sg_dma_address(data->sg), sizeof(u32))) { | ||
dev_err(mmc_dev(host->mmc), | ||
"unaligned last scatterlist: ofst:%x length:%d\n", | ||
data->sg->offset, data->sg->length); | ||
return -EINVAL; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int _sdmmc_idma_prep_data(struct mmci_host *host, | ||
struct mmc_data *data) | ||
{ | ||
int n_elem; | ||
|
||
n_elem = dma_map_sg(mmc_dev(host->mmc), | ||
data->sg, | ||
data->sg_len, | ||
mmc_get_dma_dir(data)); | ||
|
||
if (!n_elem) { | ||
dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n"); | ||
return -EINVAL; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int sdmmc_idma_prep_data(struct mmci_host *host, | ||
struct mmc_data *data, bool next) | ||
{ | ||
/* Check if job is already prepared. */ | ||
if (!next && data->host_cookie == host->next_cookie) | ||
return 0; | ||
|
||
return _sdmmc_idma_prep_data(host, data); | ||
} | ||
|
||
static void sdmmc_idma_unprep_data(struct mmci_host *host, | ||
struct mmc_data *data, int err) | ||
{ | ||
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, | ||
mmc_get_dma_dir(data)); | ||
} | ||
|
||
static int sdmmc_idma_setup(struct mmci_host *host) | ||
{ | ||
struct sdmmc_priv *idma; | ||
|
||
idma = devm_kzalloc(mmc_dev(host->mmc), sizeof(*idma), GFP_KERNEL); | ||
if (!idma) | ||
return -ENOMEM; | ||
|
||
host->dma_priv = idma; | ||
|
||
if (host->variant->dma_lli) { | ||
idma->sg_cpu = dmam_alloc_coherent(mmc_dev(host->mmc), | ||
SDMMC_LLI_BUF_LEN, | ||
&idma->sg_dma, GFP_KERNEL); | ||
if (!idma->sg_cpu) { | ||
dev_err(mmc_dev(host->mmc), | ||
"Failed to alloc IDMA descriptor\n"); | ||
return -ENOMEM; | ||
} | ||
host->mmc->max_segs = SDMMC_LLI_BUF_LEN / | ||
sizeof(struct sdmmc_lli_desc); | ||
host->mmc->max_seg_size = host->variant->stm32_idmabsize_mask; | ||
} else { | ||
host->mmc->max_segs = 1; | ||
host->mmc->max_seg_size = host->mmc->max_req_size; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int sdmmc_idma_start(struct mmci_host *host, unsigned int *datactrl) | ||
|
||
{ | ||
struct sdmmc_priv *idma = host->dma_priv; | ||
struct sdmmc_lli_desc *desc = (struct sdmmc_lli_desc *)idma->sg_cpu; | ||
struct mmc_data *data = host->data; | ||
struct scatterlist *sg; | ||
int i; | ||
|
||
if (!host->variant->dma_lli || data->sg_len == 1) { | ||
writel_relaxed(sg_dma_address(data->sg), | ||
host->base + MMCI_STM32_IDMABASE0R); | ||
writel_relaxed(MMCI_STM32_IDMAEN, | ||
host->base + MMCI_STM32_IDMACTRLR); | ||
return 0; | ||
} | ||
|
||
for_each_sg(data->sg, sg, data->sg_len, i) { | ||
desc[i].idmalar = (i + 1) * sizeof(struct sdmmc_lli_desc); | ||
desc[i].idmalar |= MMCI_STM32_ULA | MMCI_STM32_ULS | ||
| MMCI_STM32_ABR; | ||
desc[i].idmabase = sg_dma_address(sg); | ||
desc[i].idmasize = sg_dma_len(sg); | ||
} | ||
|
||
/* notice the end of link list */ | ||
desc[data->sg_len - 1].idmalar &= ~MMCI_STM32_ULA; | ||
|
||
dma_wmb(); | ||
writel_relaxed(idma->sg_dma, host->base + MMCI_STM32_IDMABAR); | ||
writel_relaxed(desc[0].idmalar, host->base + MMCI_STM32_IDMALAR); | ||
writel_relaxed(desc[0].idmabase, host->base + MMCI_STM32_IDMABASE0R); | ||
writel_relaxed(desc[0].idmasize, host->base + MMCI_STM32_IDMABSIZER); | ||
writel_relaxed(MMCI_STM32_IDMAEN | MMCI_STM32_IDMALLIEN, | ||
host->base + MMCI_STM32_IDMACTRLR); | ||
|
||
return 0; | ||
} | ||
|
||
static void sdmmc_idma_finalize(struct mmci_host *host, struct mmc_data *data) | ||
{ | ||
writel_relaxed(0, host->base + MMCI_STM32_IDMACTRLR); | ||
} | ||
|
||
static void mmci_sdmmc_set_clkreg(struct mmci_host *host, unsigned int desired) | ||
{ | ||
unsigned int clk = 0, ddr = 0; | ||
|
||
if (host->mmc->ios.timing == MMC_TIMING_MMC_DDR52 || | ||
host->mmc->ios.timing == MMC_TIMING_UHS_DDR50) | ||
ddr = MCI_STM32_CLK_DDR; | ||
|
||
/* | ||
* cclk = mclk / (2 * clkdiv) | ||
* clkdiv 0 => bypass | ||
* in ddr mode bypass is not possible | ||
*/ | ||
if (desired) { | ||
if (desired >= host->mclk && !ddr) { | ||
host->cclk = host->mclk; | ||
} else { | ||
clk = DIV_ROUND_UP(host->mclk, 2 * desired); | ||
if (clk > MCI_STM32_CLK_CLKDIV_MSK) | ||
clk = MCI_STM32_CLK_CLKDIV_MSK; | ||
host->cclk = host->mclk / (2 * clk); | ||
} | ||
} else { | ||
/* | ||
* while power-on phase the clock can't be define to 0, | ||
* Only power-off and power-cyc deactivate the clock. | ||
* if desired clock is 0, set max divider | ||
*/ | ||
clk = MCI_STM32_CLK_CLKDIV_MSK; | ||
host->cclk = host->mclk / (2 * clk); | ||
} | ||
|
||
/* Set actual clock for debug */ | ||
if (host->mmc->ios.power_mode == MMC_POWER_ON) | ||
host->mmc->actual_clock = host->cclk; | ||
else | ||
host->mmc->actual_clock = 0; | ||
|
||
if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_4) | ||
clk |= MCI_STM32_CLK_WIDEBUS_4; | ||
if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_8) | ||
clk |= MCI_STM32_CLK_WIDEBUS_8; | ||
|
||
clk |= MCI_STM32_CLK_HWFCEN; | ||
clk |= host->clk_reg_add; | ||
clk |= ddr; | ||
|
||
/* | ||
* SDMMC_FBCK is selected when an external Delay Block is needed | ||
* with SDR104. | ||
*/ | ||
if (host->mmc->ios.timing >= MMC_TIMING_UHS_SDR50) { | ||
clk |= MCI_STM32_CLK_BUSSPEED; | ||
if (host->mmc->ios.timing == MMC_TIMING_UHS_SDR104) { | ||
clk &= ~MCI_STM32_CLK_SEL_MSK; | ||
clk |= MCI_STM32_CLK_SELFBCK; | ||
} | ||
} | ||
|
||
mmci_write_clkreg(host, clk); | ||
} | ||
|
||
static void mmci_sdmmc_set_pwrreg(struct mmci_host *host, unsigned int pwr) | ||
{ | ||
struct mmc_ios ios = host->mmc->ios; | ||
|
||
pwr = host->pwr_reg_add; | ||
|
||
if (ios.power_mode == MMC_POWER_OFF) { | ||
/* Only a reset could power-off sdmmc */ | ||
reset_control_assert(host->rst); | ||
udelay(2); | ||
reset_control_deassert(host->rst); | ||
|
||
/* | ||
* Set the SDMMC in Power-cycle state. | ||
* This will make that the SDMMC_D[7:0], SDMMC_CMD and SDMMC_CK | ||
* are driven low, to prevent the Card from being supplied | ||
* through the signal lines. | ||
*/ | ||
mmci_write_pwrreg(host, MCI_STM32_PWR_CYC | pwr); | ||
} else if (ios.power_mode == MMC_POWER_ON) { | ||
/* | ||
* After power-off (reset): the irq mask defined in probe | ||
* functionis lost | ||
* ault irq mask (probe) must be activated | ||
*/ | ||
writel(MCI_IRQENABLE | host->variant->start_err, | ||
host->base + MMCIMASK0); | ||
|
||
/* | ||
* After a power-cycle state, we must set the SDMMC in | ||
* Power-off. The SDMMC_D[7:0], SDMMC_CMD and SDMMC_CK are | ||
* driven high. Then we can set the SDMMC to Power-on state | ||
*/ | ||
mmci_write_pwrreg(host, MCI_PWR_OFF | pwr); | ||
mdelay(1); | ||
mmci_write_pwrreg(host, MCI_PWR_ON | pwr); | ||
} | ||
} | ||
|
||
static struct mmci_host_ops sdmmc_variant_ops = { | ||
.validate_data = sdmmc_idma_validate_data, | ||
.prep_data = sdmmc_idma_prep_data, | ||
.unprep_data = sdmmc_idma_unprep_data, | ||
.dma_setup = sdmmc_idma_setup, | ||
.dma_start = sdmmc_idma_start, | ||
.dma_finalize = sdmmc_idma_finalize, | ||
.set_clkreg = mmci_sdmmc_set_clkreg, | ||
.set_pwrreg = mmci_sdmmc_set_pwrreg, | ||
}; | ||
|
||
void sdmmc_variant_init(struct mmci_host *host) | ||
{ | ||
host->ops = &sdmmc_variant_ops; | ||
} |