|
70 | 70 | #define CV18XX_SDHCI_PHY_CONFIG 0x4c
|
71 | 71 | #define CV18XX_PHY_TX_BPS BIT(0)
|
72 | 72 |
|
| 73 | +#define CV18XX_TUNE_MAX 128 |
| 74 | +#define CV18XX_TUNE_STEP 1 |
| 75 | +#define CV18XX_RETRY_TUNING_MAX 50 |
| 76 | + |
73 | 77 | /* Rockchip specific Registers */
|
74 | 78 | #define DWCMSHC_EMMC_DLL_CTRL 0x800
|
75 | 79 | #define DWCMSHC_EMMC_DLL_RXCLK 0x804
|
@@ -780,6 +784,113 @@ static void cv18xx_sdhci_reset(struct sdhci_host *host, u8 mask)
|
780 | 784 | sdhci_writel(host, val, priv->vendor_specific_area1 + CV18XX_SDHCI_PHY_TX_RX_DLY);
|
781 | 785 | }
|
782 | 786 |
|
| 787 | +static void cv18xx_sdhci_set_tap(struct sdhci_host *host, int tap) |
| 788 | +{ |
| 789 | + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| 790 | + struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); |
| 791 | + u16 clk; |
| 792 | + u32 val; |
| 793 | + |
| 794 | + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); |
| 795 | + clk &= ~SDHCI_CLOCK_CARD_EN; |
| 796 | + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); |
| 797 | + |
| 798 | + val = sdhci_readl(host, priv->vendor_specific_area1 + CV18XX_SDHCI_MSHC_CTRL); |
| 799 | + val &= ~CV18XX_LATANCY_1T; |
| 800 | + sdhci_writel(host, val, priv->vendor_specific_area1 + CV18XX_SDHCI_MSHC_CTRL); |
| 801 | + |
| 802 | + val = (FIELD_PREP(CV18XX_PHY_TX_DLY_MSK, 0) | |
| 803 | + FIELD_PREP(CV18XX_PHY_TX_SRC_MSK, CV18XX_PHY_TX_SRC_INVERT_CLK_TX) | |
| 804 | + FIELD_PREP(CV18XX_PHY_RX_DLY_MSK, tap)); |
| 805 | + sdhci_writel(host, val, priv->vendor_specific_area1 + CV18XX_SDHCI_PHY_TX_RX_DLY); |
| 806 | + |
| 807 | + sdhci_writel(host, 0, priv->vendor_specific_area1 + CV18XX_SDHCI_PHY_CONFIG); |
| 808 | + |
| 809 | + clk |= SDHCI_CLOCK_CARD_EN; |
| 810 | + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); |
| 811 | + usleep_range(1000, 2000); |
| 812 | +} |
| 813 | + |
| 814 | +static int cv18xx_retry_tuning(struct mmc_host *mmc, u32 opcode, int *cmd_error) |
| 815 | +{ |
| 816 | + int ret, retry = 0; |
| 817 | + |
| 818 | + while (retry < CV18XX_RETRY_TUNING_MAX) { |
| 819 | + ret = mmc_send_tuning(mmc, opcode, NULL); |
| 820 | + if (ret) |
| 821 | + return ret; |
| 822 | + retry++; |
| 823 | + } |
| 824 | + |
| 825 | + return 0; |
| 826 | +} |
| 827 | + |
| 828 | +static void cv18xx_sdhci_post_tuning(struct sdhci_host *host) |
| 829 | +{ |
| 830 | + u32 val; |
| 831 | + |
| 832 | + val = sdhci_readl(host, SDHCI_INT_STATUS); |
| 833 | + val |= SDHCI_INT_DATA_AVAIL; |
| 834 | + sdhci_writel(host, val, SDHCI_INT_STATUS); |
| 835 | + |
| 836 | + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); |
| 837 | +} |
| 838 | + |
| 839 | +static int cv18xx_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode) |
| 840 | +{ |
| 841 | + int min, max, avg, ret; |
| 842 | + int win_length, target_min, target_max, target_win_length; |
| 843 | + |
| 844 | + min = max = 0; |
| 845 | + target_win_length = 0; |
| 846 | + |
| 847 | + sdhci_reset_tuning(host); |
| 848 | + |
| 849 | + while (max < CV18XX_TUNE_MAX) { |
| 850 | + /* find the mininum delay first which can pass tuning */ |
| 851 | + while (min < CV18XX_TUNE_MAX) { |
| 852 | + cv18xx_sdhci_set_tap(host, min); |
| 853 | + if (!cv18xx_retry_tuning(host->mmc, opcode, NULL)) |
| 854 | + break; |
| 855 | + min += CV18XX_TUNE_STEP; |
| 856 | + } |
| 857 | + |
| 858 | + /* find the maxinum delay which can not pass tuning */ |
| 859 | + max = min + CV18XX_TUNE_STEP; |
| 860 | + while (max < CV18XX_TUNE_MAX) { |
| 861 | + cv18xx_sdhci_set_tap(host, max); |
| 862 | + if (cv18xx_retry_tuning(host->mmc, opcode, NULL)) { |
| 863 | + max -= CV18XX_TUNE_STEP; |
| 864 | + break; |
| 865 | + } |
| 866 | + max += CV18XX_TUNE_STEP; |
| 867 | + } |
| 868 | + |
| 869 | + win_length = max - min + 1; |
| 870 | + /* get the largest pass window */ |
| 871 | + if (win_length > target_win_length) { |
| 872 | + target_win_length = win_length; |
| 873 | + target_min = min; |
| 874 | + target_max = max; |
| 875 | + } |
| 876 | + |
| 877 | + /* continue to find the next pass window */ |
| 878 | + min = max + CV18XX_TUNE_STEP; |
| 879 | + } |
| 880 | + |
| 881 | + cv18xx_sdhci_post_tuning(host); |
| 882 | + |
| 883 | + /* use average delay to get the best timing */ |
| 884 | + avg = (target_min + target_max) / 2; |
| 885 | + cv18xx_sdhci_set_tap(host, avg); |
| 886 | + ret = mmc_send_tuning(host->mmc, opcode, NULL); |
| 887 | + |
| 888 | + dev_dbg(mmc_dev(host->mmc), "tuning %s at 0x%x ret %d\n", |
| 889 | + ret ? "failed" : "passed", avg, ret); |
| 890 | + |
| 891 | + return ret; |
| 892 | +} |
| 893 | + |
783 | 894 | static const struct sdhci_ops sdhci_dwcmshc_ops = {
|
784 | 895 | .set_clock = sdhci_set_clock,
|
785 | 896 | .set_bus_width = sdhci_set_bus_width,
|
@@ -817,6 +928,7 @@ static const struct sdhci_ops sdhci_dwcmshc_cv18xx_ops = {
|
817 | 928 | .get_max_clock = dwcmshc_get_max_clock,
|
818 | 929 | .reset = cv18xx_sdhci_reset,
|
819 | 930 | .adma_write_desc = dwcmshc_adma_write_desc,
|
| 931 | + .platform_execute_tuning = cv18xx_sdhci_execute_tuning, |
820 | 932 | };
|
821 | 933 |
|
822 | 934 | static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
|
|
0 commit comments