diff options
author | Dong Aisheng <b29396@freescale.com> | 2013-09-13 07:11:34 -0400 |
---|---|---|
committer | Chris Ball <cjb@laptop.org> | 2013-09-26 07:57:23 -0400 |
commit | 0322191e62984b94d1b2ae5ff322112e1fa1ef1a (patch) | |
tree | e58c406d1dcbf45d77a6cc7fb1acbda8179001a7 | |
parent | fed2f6e2d42e4bcdec5ea357e7a9db8602744753 (diff) |
mmc: sdhci-esdhc-imx: add sd3.0 SDR clock tuning support
Freescale i.MX6Q/DL uSDHC clock tuning progress is a little different from
the standard tuning process defined in host controller spec v3.0.
Thus we use platform_execute_tuning instead of standard sdhci tuning.
The main difference are:
1) not only generate Buffer Read Ready interrupt when tuning is performing.
It generates all other DATA interrupts like the normal data command.
2) SDHCI_CTRL_EXEC_TUNING is not automatically cleared by HW,
instead it's controlled by SW.
3) SDHCI_CTRL_TUNED_CLK is not automatically set by HW,
it's controlled by SW.
4) the clock delay for every tuning is set by SW.
Signed-off-by: Dong Aisheng <b29396@freescale.com>
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Signed-off-by: Chris Ball <cjb@laptop.org>
-rw-r--r-- | drivers/mmc/host/sdhci-esdhc-imx.c | 196 |
1 files changed, 195 insertions, 1 deletions
diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 37fafd7df964..f906c206901d 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c | |||
@@ -34,13 +34,25 @@ | |||
34 | /* VENDOR SPEC register */ | 34 | /* VENDOR SPEC register */ |
35 | #define ESDHC_VENDOR_SPEC 0xc0 | 35 | #define ESDHC_VENDOR_SPEC 0xc0 |
36 | #define ESDHC_VENDOR_SPEC_SDIO_QUIRK (1 << 1) | 36 | #define ESDHC_VENDOR_SPEC_SDIO_QUIRK (1 << 1) |
37 | #define ESDHC_VENDOR_SPEC_VSELECT (1 << 1) | ||
37 | #define ESDHC_VENDOR_SPEC_FRC_SDCLK_ON (1 << 8) | 38 | #define ESDHC_VENDOR_SPEC_FRC_SDCLK_ON (1 << 8) |
38 | #define ESDHC_WTMK_LVL 0x44 | 39 | #define ESDHC_WTMK_LVL 0x44 |
39 | #define ESDHC_MIX_CTRL 0x48 | 40 | #define ESDHC_MIX_CTRL 0x48 |
40 | #define ESDHC_MIX_CTRL_AC23EN (1 << 7) | 41 | #define ESDHC_MIX_CTRL_AC23EN (1 << 7) |
42 | #define ESDHC_MIX_CTRL_EXE_TUNE (1 << 22) | ||
43 | #define ESDHC_MIX_CTRL_SMPCLK_SEL (1 << 23) | ||
44 | #define ESDHC_MIX_CTRL_FBCLK_SEL (1 << 25) | ||
41 | /* Bits 3 and 6 are not SDHCI standard definitions */ | 45 | /* Bits 3 and 6 are not SDHCI standard definitions */ |
42 | #define ESDHC_MIX_CTRL_SDHCI_MASK 0xb7 | 46 | #define ESDHC_MIX_CTRL_SDHCI_MASK 0xb7 |
43 | 47 | ||
48 | /* tune control register */ | ||
49 | #define ESDHC_TUNE_CTRL_STATUS 0x68 | ||
50 | #define ESDHC_TUNE_CTRL_STEP 1 | ||
51 | #define ESDHC_TUNE_CTRL_MIN 0 | ||
52 | #define ESDHC_TUNE_CTRL_MAX ((1 << 7) - 1) | ||
53 | |||
54 | #define ESDHC_TUNING_BLOCK_PATTERN_LEN 64 | ||
55 | |||
44 | /* | 56 | /* |
45 | * Our interpretation of the SDHCI_HOST_CONTROL register | 57 | * Our interpretation of the SDHCI_HOST_CONTROL register |
46 | */ | 58 | */ |
@@ -91,7 +103,7 @@ struct pltfm_imx_data { | |||
91 | MULTIBLK_IN_PROCESS, /* exact multiblock cmd in process */ | 103 | MULTIBLK_IN_PROCESS, /* exact multiblock cmd in process */ |
92 | WAIT_FOR_INT, /* sent CMD12, waiting for response INT */ | 104 | WAIT_FOR_INT, /* sent CMD12, waiting for response INT */ |
93 | } multiblock_status; | 105 | } multiblock_status; |
94 | 106 | u32 uhs_mode; | |
95 | }; | 107 | }; |
96 | 108 | ||
97 | static struct platform_device_id imx_esdhc_devtype[] = { | 109 | static struct platform_device_id imx_esdhc_devtype[] = { |
@@ -165,6 +177,16 @@ static u32 esdhc_readl_le(struct sdhci_host *host, int reg) | |||
165 | struct pltfm_imx_data *imx_data = pltfm_host->priv; | 177 | struct pltfm_imx_data *imx_data = pltfm_host->priv; |
166 | u32 val = readl(host->ioaddr + reg); | 178 | u32 val = readl(host->ioaddr + reg); |
167 | 179 | ||
180 | if (unlikely(reg == SDHCI_PRESENT_STATE)) { | ||
181 | u32 fsl_prss = val; | ||
182 | /* save the least 20 bits */ | ||
183 | val = fsl_prss & 0x000FFFFF; | ||
184 | /* move dat[0-3] bits */ | ||
185 | val |= (fsl_prss & 0x0F000000) >> 4; | ||
186 | /* move cmd line bit */ | ||
187 | val |= (fsl_prss & 0x00800000) << 1; | ||
188 | } | ||
189 | |||
168 | if (unlikely(reg == SDHCI_CAPABILITIES)) { | 190 | if (unlikely(reg == SDHCI_CAPABILITIES)) { |
169 | /* In FSL esdhc IC module, only bit20 is used to indicate the | 191 | /* In FSL esdhc IC module, only bit20 is used to indicate the |
170 | * ADMA2 capability of esdhc, but this bit is messed up on | 192 | * ADMA2 capability of esdhc, but this bit is messed up on |
@@ -179,6 +201,17 @@ static u32 esdhc_readl_le(struct sdhci_host *host, int reg) | |||
179 | } | 201 | } |
180 | } | 202 | } |
181 | 203 | ||
204 | if (unlikely(reg == SDHCI_CAPABILITIES_1) && is_imx6q_usdhc(imx_data)) | ||
205 | val = SDHCI_SUPPORT_DDR50 | SDHCI_SUPPORT_SDR104 | ||
206 | | SDHCI_SUPPORT_SDR50; | ||
207 | |||
208 | if (unlikely(reg == SDHCI_MAX_CURRENT) && is_imx6q_usdhc(imx_data)) { | ||
209 | val = 0; | ||
210 | val |= 0xFF << SDHCI_MAX_CURRENT_330_SHIFT; | ||
211 | val |= 0xFF << SDHCI_MAX_CURRENT_300_SHIFT; | ||
212 | val |= 0xFF << SDHCI_MAX_CURRENT_180_SHIFT; | ||
213 | } | ||
214 | |||
182 | if (unlikely(reg == SDHCI_INT_STATUS)) { | 215 | if (unlikely(reg == SDHCI_INT_STATUS)) { |
183 | if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) { | 216 | if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) { |
184 | val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR; | 217 | val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR; |
@@ -257,6 +290,8 @@ static u16 esdhc_readw_le(struct sdhci_host *host, int reg) | |||
257 | { | 290 | { |
258 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | 291 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
259 | struct pltfm_imx_data *imx_data = pltfm_host->priv; | 292 | struct pltfm_imx_data *imx_data = pltfm_host->priv; |
293 | u16 ret = 0; | ||
294 | u32 val; | ||
260 | 295 | ||
261 | if (unlikely(reg == SDHCI_HOST_VERSION)) { | 296 | if (unlikely(reg == SDHCI_HOST_VERSION)) { |
262 | reg ^= 2; | 297 | reg ^= 2; |
@@ -269,6 +304,25 @@ static u16 esdhc_readw_le(struct sdhci_host *host, int reg) | |||
269 | } | 304 | } |
270 | } | 305 | } |
271 | 306 | ||
307 | if (unlikely(reg == SDHCI_HOST_CONTROL2)) { | ||
308 | val = readl(host->ioaddr + ESDHC_VENDOR_SPEC); | ||
309 | if (val & ESDHC_VENDOR_SPEC_VSELECT) | ||
310 | ret |= SDHCI_CTRL_VDD_180; | ||
311 | |||
312 | if (is_imx6q_usdhc(imx_data)) { | ||
313 | val = readl(host->ioaddr + ESDHC_MIX_CTRL); | ||
314 | if (val & ESDHC_MIX_CTRL_EXE_TUNE) | ||
315 | ret |= SDHCI_CTRL_EXEC_TUNING; | ||
316 | if (val & ESDHC_MIX_CTRL_SMPCLK_SEL) | ||
317 | ret |= SDHCI_CTRL_TUNED_CLK; | ||
318 | } | ||
319 | |||
320 | ret |= (imx_data->uhs_mode & SDHCI_CTRL_UHS_MASK); | ||
321 | ret &= ~SDHCI_CTRL_PRESET_VAL_ENABLE; | ||
322 | |||
323 | return ret; | ||
324 | } | ||
325 | |||
272 | return readw(host->ioaddr + reg); | 326 | return readw(host->ioaddr + reg); |
273 | } | 327 | } |
274 | 328 | ||
@@ -276,8 +330,32 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) | |||
276 | { | 330 | { |
277 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); | 331 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
278 | struct pltfm_imx_data *imx_data = pltfm_host->priv; | 332 | struct pltfm_imx_data *imx_data = pltfm_host->priv; |
333 | u32 new_val = 0; | ||
279 | 334 | ||
280 | switch (reg) { | 335 | switch (reg) { |
336 | case SDHCI_CLOCK_CONTROL: | ||
337 | new_val = readl(host->ioaddr + ESDHC_VENDOR_SPEC); | ||
338 | if (val & SDHCI_CLOCK_CARD_EN) | ||
339 | new_val |= ESDHC_VENDOR_SPEC_FRC_SDCLK_ON; | ||
340 | else | ||
341 | new_val &= ~ESDHC_VENDOR_SPEC_FRC_SDCLK_ON; | ||
342 | writel(new_val, host->ioaddr + ESDHC_VENDOR_SPEC); | ||
343 | return; | ||
344 | case SDHCI_HOST_CONTROL2: | ||
345 | new_val = readl(host->ioaddr + ESDHC_VENDOR_SPEC); | ||
346 | if (val & SDHCI_CTRL_VDD_180) | ||
347 | new_val |= ESDHC_VENDOR_SPEC_VSELECT; | ||
348 | else | ||
349 | new_val &= ~ESDHC_VENDOR_SPEC_VSELECT; | ||
350 | writel(new_val, host->ioaddr + ESDHC_VENDOR_SPEC); | ||
351 | imx_data->uhs_mode = val & SDHCI_CTRL_UHS_MASK; | ||
352 | new_val = readl(host->ioaddr + ESDHC_MIX_CTRL); | ||
353 | if (val & SDHCI_CTRL_TUNED_CLK) | ||
354 | new_val |= ESDHC_MIX_CTRL_SMPCLK_SEL; | ||
355 | else | ||
356 | new_val &= ~ESDHC_MIX_CTRL_SMPCLK_SEL; | ||
357 | writel(new_val , host->ioaddr + ESDHC_MIX_CTRL); | ||
358 | return; | ||
281 | case SDHCI_TRANSFER_MODE: | 359 | case SDHCI_TRANSFER_MODE: |
282 | if ((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT) | 360 | if ((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT) |
283 | && (host->cmd->opcode == SD_IO_RW_EXTENDED) | 361 | && (host->cmd->opcode == SD_IO_RW_EXTENDED) |
@@ -500,6 +578,121 @@ static int esdhc_pltfm_bus_width(struct sdhci_host *host, int width) | |||
500 | return 0; | 578 | return 0; |
501 | } | 579 | } |
502 | 580 | ||
581 | static void esdhc_prepare_tuning(struct sdhci_host *host, u32 val) | ||
582 | { | ||
583 | u32 reg; | ||
584 | |||
585 | /* FIXME: delay a bit for card to be ready for next tuning due to errors */ | ||
586 | mdelay(1); | ||
587 | |||
588 | reg = readl(host->ioaddr + ESDHC_MIX_CTRL); | ||
589 | reg |= ESDHC_MIX_CTRL_EXE_TUNE | ESDHC_MIX_CTRL_SMPCLK_SEL | | ||
590 | ESDHC_MIX_CTRL_FBCLK_SEL; | ||
591 | writel(reg, host->ioaddr + ESDHC_MIX_CTRL); | ||
592 | writel(val << 8, host->ioaddr + ESDHC_TUNE_CTRL_STATUS); | ||
593 | dev_dbg(mmc_dev(host->mmc), | ||
594 | "tunning with delay 0x%x ESDHC_TUNE_CTRL_STATUS 0x%x\n", | ||
595 | val, readl(host->ioaddr + ESDHC_TUNE_CTRL_STATUS)); | ||
596 | } | ||
597 | |||
598 | static void esdhc_request_done(struct mmc_request *mrq) | ||
599 | { | ||
600 | complete(&mrq->completion); | ||
601 | } | ||
602 | |||
603 | static int esdhc_send_tuning_cmd(struct sdhci_host *host, u32 opcode) | ||
604 | { | ||
605 | struct mmc_command cmd = {0}; | ||
606 | struct mmc_request mrq = {0}; | ||
607 | struct mmc_data data = {0}; | ||
608 | struct scatterlist sg; | ||
609 | char tuning_pattern[ESDHC_TUNING_BLOCK_PATTERN_LEN]; | ||
610 | |||
611 | cmd.opcode = opcode; | ||
612 | cmd.arg = 0; | ||
613 | cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; | ||
614 | |||
615 | data.blksz = ESDHC_TUNING_BLOCK_PATTERN_LEN; | ||
616 | data.blocks = 1; | ||
617 | data.flags = MMC_DATA_READ; | ||
618 | data.sg = &sg; | ||
619 | data.sg_len = 1; | ||
620 | |||
621 | sg_init_one(&sg, tuning_pattern, sizeof(tuning_pattern)); | ||
622 | |||
623 | mrq.cmd = &cmd; | ||
624 | mrq.cmd->mrq = &mrq; | ||
625 | mrq.data = &data; | ||
626 | mrq.data->mrq = &mrq; | ||
627 | mrq.cmd->data = mrq.data; | ||
628 | |||
629 | mrq.done = esdhc_request_done; | ||
630 | init_completion(&(mrq.completion)); | ||
631 | |||
632 | disable_irq(host->irq); | ||
633 | spin_lock(&host->lock); | ||
634 | host->mrq = &mrq; | ||
635 | |||
636 | sdhci_send_command(host, mrq.cmd); | ||
637 | |||
638 | spin_unlock(&host->lock); | ||
639 | enable_irq(host->irq); | ||
640 | |||
641 | wait_for_completion(&mrq.completion); | ||
642 | |||
643 | if (cmd.error) | ||
644 | return cmd.error; | ||
645 | if (data.error) | ||
646 | return data.error; | ||
647 | |||
648 | return 0; | ||
649 | } | ||
650 | |||
651 | static void esdhc_post_tuning(struct sdhci_host *host) | ||
652 | { | ||
653 | u32 reg; | ||
654 | |||
655 | reg = readl(host->ioaddr + ESDHC_MIX_CTRL); | ||
656 | reg &= ~ESDHC_MIX_CTRL_EXE_TUNE; | ||
657 | writel(reg, host->ioaddr + ESDHC_MIX_CTRL); | ||
658 | } | ||
659 | |||
660 | static int esdhc_executing_tuning(struct sdhci_host *host, u32 opcode) | ||
661 | { | ||
662 | int min, max, avg, ret; | ||
663 | |||
664 | /* find the mininum delay first which can pass tuning */ | ||
665 | min = ESDHC_TUNE_CTRL_MIN; | ||
666 | while (min < ESDHC_TUNE_CTRL_MAX) { | ||
667 | esdhc_prepare_tuning(host, min); | ||
668 | if (!esdhc_send_tuning_cmd(host, opcode)) | ||
669 | break; | ||
670 | min += ESDHC_TUNE_CTRL_STEP; | ||
671 | } | ||
672 | |||
673 | /* find the maxinum delay which can not pass tuning */ | ||
674 | max = min + ESDHC_TUNE_CTRL_STEP; | ||
675 | while (max < ESDHC_TUNE_CTRL_MAX) { | ||
676 | esdhc_prepare_tuning(host, max); | ||
677 | if (esdhc_send_tuning_cmd(host, opcode)) { | ||
678 | max -= ESDHC_TUNE_CTRL_STEP; | ||
679 | break; | ||
680 | } | ||
681 | max += ESDHC_TUNE_CTRL_STEP; | ||
682 | } | ||
683 | |||
684 | /* use average delay to get the best timing */ | ||
685 | avg = (min + max) / 2; | ||
686 | esdhc_prepare_tuning(host, avg); | ||
687 | ret = esdhc_send_tuning_cmd(host, opcode); | ||
688 | esdhc_post_tuning(host); | ||
689 | |||
690 | dev_dbg(mmc_dev(host->mmc), "tunning %s at 0x%x ret %d\n", | ||
691 | ret ? "failed" : "passed", avg, ret); | ||
692 | |||
693 | return ret; | ||
694 | } | ||
695 | |||
503 | static const struct sdhci_ops sdhci_esdhc_ops = { | 696 | static const struct sdhci_ops sdhci_esdhc_ops = { |
504 | .read_l = esdhc_readl_le, | 697 | .read_l = esdhc_readl_le, |
505 | .read_w = esdhc_readw_le, | 698 | .read_w = esdhc_readw_le, |
@@ -511,6 +704,7 @@ static const struct sdhci_ops sdhci_esdhc_ops = { | |||
511 | .get_min_clock = esdhc_pltfm_get_min_clock, | 704 | .get_min_clock = esdhc_pltfm_get_min_clock, |
512 | .get_ro = esdhc_pltfm_get_ro, | 705 | .get_ro = esdhc_pltfm_get_ro, |
513 | .platform_bus_width = esdhc_pltfm_bus_width, | 706 | .platform_bus_width = esdhc_pltfm_bus_width, |
707 | .platform_execute_tuning = esdhc_executing_tuning, | ||
514 | }; | 708 | }; |
515 | 709 | ||
516 | static const struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { | 710 | static const struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { |