/* Bitfields are common for all pad settings */ #define PHY_PAD_RXSEL_1V8 0x1 /* Receiver type select for 1.8V */ #define PHY_PAD_RXSEL_3V3 0x2 /* Receiver type select for 3.3V */
staticint dwcmshc_get_enable_other_clks(struct device *dev, struct dwcmshc_priv *priv, int num_clks, constchar * const clk_ids[])
{ int err;
if (num_clks > DWCMSHC_MAX_OTHER_CLKS) return -EINVAL;
for (int i = 0; i < num_clks; i++)
priv->other_clks[i].id = clk_ids[i];
err = devm_clk_bulk_get_optional(dev, num_clks, priv->other_clks); if (err) {
dev_err(dev, "failed to get clocks %d\n", err); return err;
}
err = clk_bulk_prepare_enable(num_clks, priv->other_clks); if (err)
dev_err(dev, "failed to enable clocks %d\n", err);
priv->num_other_clks = num_clks;
return err;
}
/* * If DMA addr spans 128MB boundary, we split the DMA transfer into two * so that each DMA transfer doesn't exceed the boundary.
*/ staticvoid dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc,
dma_addr_t addr, int len, unsignedint cmd)
{ int tmplen, offset;
/* * No matter V4 is enabled or not, ARGUMENT2 register is 32-bit * block count register which doesn't support stuff bits of * CMD23 argument on dwcmsch host controller.
*/ if (mrq->sbc && (mrq->sbc->arg & SDHCI_DWCMSHC_ARG2_STUFF))
host->flags &= ~SDHCI_AUTO_CMD23; else
host->flags |= SDHCI_AUTO_CMD23;
}
/* deassert phy reset & set tx drive strength */
val = PHY_CNFG_RSTN_DEASSERT;
val |= FIELD_PREP(PHY_CNFG_PAD_SP_MASK, PHY_CNFG_PAD_SP);
val |= FIELD_PREP(PHY_CNFG_PAD_SN_MASK, PHY_CNFG_PAD_SN);
sdhci_writel(host, val, PHY_CNFG_R);
/* disable delay line */
sdhci_writeb(host, PHY_SDCLKDL_CNFG_UPDATE, PHY_SDCLKDL_CNFG_R);
/* set delay line */
sdhci_writeb(host, priv->delay_line, PHY_SDCLKDL_DC_R);
sdhci_writeb(host, PHY_DLL_CNFG2_JUMPSTEP, PHY_DLL_CNFG2_R);
/* enable delay lane */
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
val &= ~(PHY_SDCLKDL_CNFG_UPDATE);
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
/* configure phy pads */
val = rxsel;
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLUP);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
val = rxsel;
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLDOWN);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
/* enable data strobe mode */ if (rxsel == PHY_PAD_RXSEL_1V8) {
u8 sel = FIELD_PREP(PHY_DLLDL_CNFG_SLV_INPSEL_MASK, PHY_DLLDL_CNFG_SLV_INPSEL);
/* * Tuning can leave the IP in an active state (Buffer Read Enable bit * set) which prevents the entry to low power states (i.e. S0i3). Data * reset will clear it.
*/
sdhci_reset(host, SDHCI_RESET_DATA);
return 0;
}
static u32 dwcmshc_cqe_irq_handler(struct sdhci_host *host, u32 intmask)
{ int cmd_error = 0; int data_error = 0;
if (!sdhci_cqe_irq(host, intmask, &cmd_error, &data_error)) return intmask;
/* * The "DesignWare Cores Mobile Storage Host Controller * DWC_mshc / DWC_mshc_lite Databook" says: * when Host Version 4 Enable" is 1 in Host Control 2 register, * SDHCI_CTRL_ADMA32 bit means ADMA2 is selected. * Selection of 32-bit/64-bit System Addressing: * either 32-bit or 64-bit system addressing is selected by * 64-bit Addressing bit in Host Control 2 register. * * On the other hand the "DesignWare Cores Mobile Storage Host * Controller DWC_mshc / DWC_mshc_lite User Guide" says, that we have to * set DMA_SEL to ADMA2 _only_ mode in the Host Control 2 register.
*/
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
ctrl &= ~SDHCI_CTRL_DMA_MASK;
ctrl |= SDHCI_CTRL_ADMA32;
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
}
staticvoid dwcmshc_set_tran_desc(struct cqhci_host *cq_host, u8 **desc,
dma_addr_t addr, int len, bool end, bool dma64)
{ int tmplen, offset;
if (clock <= 52000000) { /* * Disable DLL and reset both of sample and drive clock. * The bypass bit and start bit need to be set if DLL is not locked.
*/
sdhci_writel(host, DWCMSHC_EMMC_DLL_BYPASS | DWCMSHC_EMMC_DLL_START, DWCMSHC_EMMC_DLL_CTRL);
sdhci_writel(host, DLL_RXCLK_ORI_GATE, DWCMSHC_EMMC_DLL_RXCLK);
sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_TXCLK);
sdhci_writel(host, 0, DECMSHC_EMMC_DLL_CMDOUT); /* * Before switching to hs400es mode, the driver will enable * enhanced strobe first. PHY needs to configure the parameters * of enhanced strobe first.
*/
extra = DWCMSHC_EMMC_DLL_DLYENA |
DLL_STRBIN_DELAY_NUM_SEL |
DLL_STRBIN_DELAY_NUM_DEFAULT << DLL_STRBIN_DELAY_NUM_OFFSET;
sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_STRBIN); return;
}
/* * We shouldn't set DLL_RXCLK_NO_INVERTER for identify mode but * we must set it in higher speed mode.
*/
extra = DWCMSHC_EMMC_DLL_DLYENA; if (priv->devtype == DWCMSHC_RK3568)
extra |= DLL_RXCLK_NO_INVERTER << DWCMSHC_EMMC_DLL_RXCLK_SRCSEL;
sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_RXCLK);
priv = devm_kzalloc(dev, sizeof(struct rk35xx_priv), GFP_KERNEL); if (!priv) return -ENOMEM;
if (of_device_is_compatible(dev->of_node, "rockchip,rk3588-dwcmshc"))
priv->devtype = DWCMSHC_RK3588; else
priv->devtype = DWCMSHC_RK3568;
priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc)); if (IS_ERR(priv->reset)) {
err = PTR_ERR(priv->reset);
dev_err(mmc_dev(host->mmc), "failed to get reset control %d\n", err); return err;
}
err = dwcmshc_get_enable_other_clks(mmc_dev(host->mmc), dwc_priv,
ARRAY_SIZE(clk_ids), clk_ids); if (err) return err;
if (of_property_read_u8(mmc_dev(host->mmc)->of_node, "rockchip,txclk-tapnum",
&priv->txclk_tapnum))
priv->txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT;
staticvoid dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
{ /* * Don't support highspeed bus mode with low clk speed as we * cannot use DLL for this condition.
*/ if (host->mmc->f_max <= 52000000) {
dev_info(mmc_dev(host->mmc), "Disabling HS200/HS400, frequency too low (%d)\n",
host->mmc->f_max);
host->mmc->caps2 &= ~(MMC_CAP2_HS200 | MMC_CAP2_HS400);
host->mmc->caps &= ~(MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR);
}
}
/* * This works around the design of the RK3576's power domains, which * makes the PD_NVM power domain, which the sdhci controller on the * RK3576 is in, never come back the same way once it's run-time * suspended once. This can happen during early kernel boot if no driver * is using either PD_NVM or its child power domain PD_SDGMAC for a * short moment, leading to it being turned off to save power. By * keeping it on, sdhci suspending won't lead to PD_NVM becoming a * candidate for getting turned off.
*/
ret = dev_pm_genpd_rpm_always_on(dev, true); if (ret && ret != -EOPNOTSUPP)
dev_warn(dev, "failed to set PD rpm always on, SoC may hang later: %pe\n",
ERR_PTR(ret));
sdhci_writeb(host, FIELD_PREP(PHY_ATDL_CNFG_INPSEL_MASK, PHY_ATDL_CNFG_INPSEL),
PHY_ATDL_CNFG_R);
val = sdhci_readl(host, priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
/* * configure tuning settings: * - center phase select code driven in block gap interval * - disable reporting of framing errors * - disable software managed tuning * - disable user selection of sampling window edges, * instead tuning calculated edges are used
*/
val &= ~(AT_CTRL_CI_SEL | AT_CTRL_RPT_TUNE_ERR | AT_CTRL_SW_TUNE_EN |
FIELD_PREP(AT_CTRL_WIN_EDGE_SEL_MASK, AT_CTRL_WIN_EDGE_SEL));
/* * configure tuning settings: * - enable auto-tuning * - enable sampling window threshold * - stop clocks during phase code change * - set max latency in cycles between tx and rx clocks * - set max latency in cycles to switch output phase * - set max sampling window threshold value
*/
val |= AT_CTRL_AT_EN | AT_CTRL_SWIN_TH_EN | AT_CTRL_TUNE_CLK_STOP_EN;
val |= FIELD_PREP(AT_CTRL_PRE_CHANGE_DLY_MASK, AT_CTRL_PRE_CHANGE_DLY);
val |= FIELD_PREP(AT_CTRL_POST_CHANGE_DLY_MASK, AT_CTRL_POST_CHANGE_DLY);
val |= FIELD_PREP(AT_CTRL_SWIN_TH_VAL_MASK, AT_CTRL_SWIN_TH_VAL);
/* The T-Head 1520 SoC does not comply with the SDHCI specification * regarding the "Software Reset for CMD line should clear 'Command * Complete' in the Normal Interrupt Status Register." Clear the bit * here to compensate for this quirk.
*/ if (mask & SDHCI_RESET_CMD)
sdhci_writel(host, SDHCI_INT_RESPONSE, SDHCI_INT_STATUS);
/* * start_signal_voltage_switch() will try 3.3V first * then 1.8V. Use SDHCI_SIGNALING_180 rather than * SDHCI_SIGNALING_330 to avoid setting voltage to 3.3V * in sdhci_start_signal_voltage_switch().
*/ if (dwc_priv->flags & FLAG_IO_FIXED_1V8) {
host->flags &= ~SDHCI_SIGNALING_330;
host->flags |= SDHCI_SIGNALING_180;
}
staticint cv18xx_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
{ int min, max, avg, ret; int win_length, target_min, target_max, target_win_length;
min = max = 0;
target_win_length = 0;
sdhci_reset_tuning(host);
while (max < CV18XX_TUNE_MAX) { /* find the mininum delay first which can pass tuning */ while (min < CV18XX_TUNE_MAX) {
cv18xx_sdhci_set_tap(host, min); if (!cv18xx_retry_tuning(host->mmc, opcode, NULL)) break;
min += CV18XX_TUNE_STEP;
}
/* find the maxinum delay which can not pass tuning */
max = min + CV18XX_TUNE_STEP; while (max < CV18XX_TUNE_MAX) {
cv18xx_sdhci_set_tap(host, max); if (cv18xx_retry_tuning(host->mmc, opcode, NULL)) {
max -= CV18XX_TUNE_STEP; break;
}
max += CV18XX_TUNE_STEP;
}
win_length = max - min + 1; /* get the largest pass window */ if (win_length > target_win_length) {
target_win_length = win_length;
target_min = min;
target_max = max;
}
/* continue to find the next pass window */
min = max + CV18XX_TUNE_STEP;
}
cv18xx_sdhci_post_tuning(host);
/* use average delay to get the best timing */
avg = (target_min + target_max) / 2;
cv18xx_sdhci_set_tap(host, avg);
ret = mmc_send_tuning(host->mmc, opcode, NULL);
dev_dbg(mmc_dev(host->mmc), "tuning %s at 0x%x ret %d\n",
ret ? "failed" : "passed", avg, ret);
/* Asset phy reset & set tx drive strength */
val = sdhci_readl(host, PHY_CNFG_R);
val &= ~PHY_CNFG_RSTN_DEASSERT;
val |= FIELD_PREP(PHY_CNFG_PHY_PWRGOOD_MASK, 1);
val |= FIELD_PREP(PHY_CNFG_PAD_SP_MASK, PHY_CNFG_PAD_SP_SG2042);
val |= FIELD_PREP(PHY_CNFG_PAD_SN_MASK, PHY_CNFG_PAD_SN_SG2042);
sdhci_writel(host, val, PHY_CNFG_R);
/* Configure phy pads */
val = PHY_PAD_RXSEL_3V3;
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLUP);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042);
sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
val = PHY_PAD_RXSEL_3V3;
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042);
sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
val = PHY_PAD_RXSEL_3V3;
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLDOWN);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042);
sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
/* Configure delay line */ /* Enable fixed delay */
sdhci_writeb(host, PHY_SDCLKDL_CNFG_EXTDLY_EN, PHY_SDCLKDL_CNFG_R); /* * Set delay line. * Its recommended that bit UPDATE_DC[4] is 1 when SDCLKDL_DC is being written. * Ensure UPDATE_DC[4] is '0' when not updating code.
*/
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
val |= PHY_SDCLKDL_CNFG_UPDATE;
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R); /* Add 10 * 70ps = 0.7ns for output delay */
sdhci_writeb(host, 10, PHY_SDCLKDL_DC_R);
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
val &= ~(PHY_SDCLKDL_CNFG_UPDATE);
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
/* Set SMPLDL_CNFG, Bypass */
sdhci_writeb(host, PHY_SMPLDL_CNFG_BYPASS_EN, PHY_SMPLDL_CNFG_R);
/* Set ATDL_CNFG, tuning clk not use for init */
val = FIELD_PREP(PHY_ATDL_CNFG_INPSEL_MASK, PHY_ATDL_CNFG_INPSEL_SG2042);
sdhci_writeb(host, val, PHY_ATDL_CNFG_R);
/* Deasset phy reset */
val = sdhci_readl(host, PHY_CNFG_R);
val |= PHY_CNFG_RSTN_DEASSERT;
sdhci_writel(host, val, PHY_CNFG_R);
}
host->mmc->caps2 |= MMC_CAP2_CQE | MMC_CAP2_CQE_DCMD;
cq_host = devm_kzalloc(&pdev->dev, sizeof(*cq_host), GFP_KERNEL); if (!cq_host) {
dev_err(mmc_dev(host->mmc), "Unable to setup CQE: not enough memory\n"); goto dsbl_cqe_caps;
}
/* * For dwcmshc host controller we have to enable internal clock * before access to some registers from Vendor Specific Area 2.
*/
clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
clk |= SDHCI_CLOCK_INT_EN;
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); if (!(clk & SDHCI_CLOCK_INT_EN)) {
dev_err(mmc_dev(host->mmc), "Unable to setup CQE: internal clock enable error\n"); goto free_cq_host;
}
pltfm_data = device_get_match_data(&pdev->dev); if (!pltfm_data) {
dev_err(&pdev->dev, "Error: No device match data found\n"); return -ENODEV;
}
host = sdhci_pltfm_init(pdev, &pltfm_data->pdata, sizeof(struct dwcmshc_priv)); if (IS_ERR(host)) return PTR_ERR(host);
/* * extra adma table cnt for cross 128M boundary handling.
*/
extra = DIV_ROUND_UP_ULL(dma_get_required_mask(dev), SZ_128M); if (extra > SDHCI_MAX_SEGS)
extra = SDHCI_MAX_SEGS;
host->adma_table_cnt += extra;
if (dev->of_node) {
pltfm_host->clk = devm_clk_get(dev, "core"); if (IS_ERR(pltfm_host->clk)) return dev_err_probe(dev, PTR_ERR(pltfm_host->clk), "failed to get core clk\n");
err = clk_prepare_enable(pltfm_host->clk); if (err) return err;
priv->bus_clk = devm_clk_get(dev, "bus"); if (!IS_ERR(priv->bus_clk))
clk_prepare_enable(priv->bus_clk);
}
err = mmc_of_parse(host->mmc); if (err) goto err_clk;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.