Enable SDMMC calibration to determine the best setting for drvsel and smplsel. Calibration will be triggered if the drvsel and smplsel node are not available in DTS.
Signed-off-by: Chin Liang See <cl...@altera.com> Cc: Dinh Nguyen <dingu...@opensource.altera.com> Cc: Dinh Nguyen <dinh.li...@gmail.com> Cc: Pavel Machek <pa...@denx.de> Cc: Marek Vasut <ma...@denx.de> Cc: Stefan Roese <s...@denx.de> Cc: Pantelis Antoniou <pantelis.anton...@konsulko.com> Cc: Simon Glass <s...@chromium.org> Cc: Jaehoon Chung <jh80.ch...@samsung.com> --- Changes for v4 - Calibration only run if node not in DTS Changes for v3 - Remove the && ok as its redundant Changes for v2 - Using standard error return macro - Split to small function to avoid deep identation - Fix coding standard --- drivers/mmc/socfpga_dw_mmc.c | 208 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/socfpga_dw_mmc.c b/drivers/mmc/socfpga_dw_mmc.c index 2bd0ebd..a8e2660 100644 --- a/drivers/mmc/socfpga_dw_mmc.c +++ b/drivers/mmc/socfpga_dw_mmc.c @@ -13,6 +13,7 @@ #include <asm/arch/dwmmc.h> #include <asm/arch/clock_manager.h> #include <asm/arch/system_manager.h> +#include "mmc_private.h" static const struct socfpga_clock_manager *clock_manager_base = (void *)SOCFPGA_CLKMGR_ADDRESS; @@ -25,7 +26,144 @@ struct dwmci_socfpga_priv_data { unsigned int smplsel; }; -static void socfpga_dwmci_clksel(struct dwmci_host *host) +/* + * rows and columns of calibration rectange. The values are based on the value + * range of drvsel and smplsel register in system manager. Note drvsel 0 is + * unusable as it has meta-stability issue. + */ +#define SOCFPGA_SD_DRVSEL 7 +#define SOCFPGA_SD_SMPLSEL 8 + +static int socfpga_dwmci_find_row_col_fit_rectangle(unsigned rect_width, + unsigned rect_height, + unsigned char cal_results[SOCFPGA_SD_DRVSEL][SOCFPGA_SD_SMPLSEL], + unsigned int *cal_row, unsigned int *cal_col) +{ + unsigned char start_row, start_col; + + /* Find the row and column where the candidate fits */ + for (start_col = 0; start_col < (SOCFPGA_SD_SMPLSEL - rect_width + 1); + start_col++) { + for (start_row = 0; + start_row < (SOCFPGA_SD_DRVSEL - rect_height + 1); + start_row++) { + unsigned ok = 1; + unsigned add_col, add_row; + + /* Determine if the rectangle fits here */ + for (add_col = 0; (add_col < rect_width); add_col++) { + for (add_row = 0; add_row < rect_height; + add_row++) { + if (!cal_results[start_row + add_row] + [start_col + add_col]) { + ok = 0; + break; + } + } + } + + /* + * Return 'middle' of rectangle in case of + * success + */ + if (ok) { + if (rect_width > 1) + rect_width--; + + if (rect_height > 1) + rect_height--; + + *cal_row = start_row + (rect_height / 2); + *cal_col = start_col + (rect_width / 2); + + return 0; + } + } + } + return -EINVAL; +} + +/* + * This function determines the largest rectangle filled with 1's and returns + * the middle. This functon can be optimized, for example using the algorithm + * from http://www.drdobbs.com/database/the-maximal-rectangle-problem/184410529 + * It currently takes less than 1ms, while creating the input data takes ~5ms + * so there is not a real need to optimize it. + */ +static int socfpga_dwmci_find_calibration_point( + unsigned char cal_results[SOCFPGA_SD_DRVSEL][SOCFPGA_SD_SMPLSEL], + unsigned int sum, unsigned int *cal_row, unsigned int *cal_col) +{ + /* Structure containing a rectangle candidate */ + struct rect_cand_t { + unsigned char height; + unsigned char width; + unsigned short area; + }; + + /* Array with the rectangle candidates */ + struct rect_cand_t rect_cands[SOCFPGA_SD_DRVSEL * SOCFPGA_SD_SMPLSEL]; + struct rect_cand_t tmp; + unsigned char cr_rect_cand = 0; + unsigned char height, width, k; + + /* No solution if all combinations fail */ + if (sum == 0) + return -EINVAL; + + /* Simple solution if all combinations pass */ + if (sum == (SOCFPGA_SD_DRVSEL * SOCFPGA_SD_SMPLSEL)) { + *cal_row = (SOCFPGA_SD_DRVSEL - 1) / 2; + *cal_col = (SOCFPGA_SD_SMPLSEL - 1) / 2; + return 0; + } + + /* + * Create list of all possible sub-rectangles, in descending area + * order + */ + for (height = SOCFPGA_SD_DRVSEL; height >= 1; height--) { + for (width = SOCFPGA_SD_SMPLSEL; width >= 1; width--) { + /* Add a new rectangle candidate */ + rect_cands[cr_rect_cand].height = height; + rect_cands[cr_rect_cand].width = width; + rect_cands[cr_rect_cand].area = height * width; + cr_rect_cand++; + + /* First candidate it always in the right position */ + if (cr_rect_cand == 1) + continue; + + /* + * Put the candidate in right location to maintain + * descending order + */ + for (k = cr_rect_cand - 1; k > 1; k--) { + if (rect_cands[k-1].area < rect_cands[k].area) { + tmp = rect_cands[k-1]; + rect_cands[k-1] = rect_cands[k]; + rect_cands[k] = tmp; + } else { + break; + } + } + } + } + + /* Try to fit the rectangle candidates, in descending area order */ + for (k = 0; k < SOCFPGA_SD_DRVSEL * SOCFPGA_SD_SMPLSEL; k++) { + if (!socfpga_dwmci_find_row_col_fit_rectangle( + rect_cands[k].width, + rect_cands[k].height, + cal_results, cal_row, cal_col)) + return 0; + } + + /* We could not fit any rectangle - return failure */ + return -EINVAL; +} + +static void socfpga_dwmci_set_clksel(struct dwmci_host *host) { struct dwmci_socfpga_priv_data *priv = host->priv; @@ -46,6 +184,70 @@ static void socfpga_dwmci_clksel(struct dwmci_host *host) CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK); } +/* + * This function calibrates the drvsel and smplsel by trying all possible + * values and selecting the best combnation + */ +void socfpga_dwmci_calibrate(struct dwmci_host *host) +{ + int err = 0; + unsigned char cal_results[SOCFPGA_SD_DRVSEL][SOCFPGA_SD_SMPLSEL]; + unsigned int sum = 0; + unsigned int row, col; + + struct mmc *mmc = host->mmc; + struct dwmci_socfpga_priv_data *priv = host->priv; + + printf("%s: Calibration started.\n", __func__); + + for (row = 0; row < SOCFPGA_SD_DRVSEL; row++) { + for (col = 0; col < SOCFPGA_SD_SMPLSEL; col++) { + priv->drvsel = row + 1; + priv->smplsel = col; + socfpga_dwmci_set_clksel(host); + cal_results[row][col] = !(mmc_set_blocklen(mmc, + mmc->read_bl_len)); + sum += cal_results[row][col]; + } + } + +#if DEBUG + debug("%s: Calibration raw data:\n", __func__); + for (row = 0; row < SOCFPGA_SD_DRVSEL; row++) { + debug("\t"); + for (col = 0; col < SOCFPGA_SD_SMPLSEL; col++) + debug("%d ", cal_results[row][col]); + debug("\n"); + } +#endif + + err = socfpga_dwmci_find_calibration_point(cal_results, sum, + &priv->drvsel, + &priv->smplsel); + + if (err) { + printf("%s: Calibration failed.\n", __func__); + hang(); + } else + printf("%s: Calibration passed: drvsel = %d " + "smplsel = %d.\n", __func__, priv->drvsel, priv->smplsel); + + socfpga_dwmci_set_clksel(host); + + /* re-write in case accidentally overwrite with junk */ + mmc_set_blocklen(mmc, mmc->read_bl_len); +} + +static void socfpga_dwmci_clksel(struct dwmci_host *host) +{ + struct dwmci_socfpga_priv_data *priv = host->priv; + + if (priv->drvsel == 0xf || priv->smplsel == 0xf) + socfpga_dwmci_calibrate(host); + else + socfpga_dwmci_set_clksel(host); +} + static int socfpga_dwmci_of_probe(const void *blob, int node, const int idx) { /* FIXME: probe from DT eventually too/ */ @@ -102,8 +304,8 @@ static int socfpga_dwmci_of_probe(const void *blob, int node, const int idx) host->bus_hz = clk; host->fifoth_val = MSIZE(0x2) | RX_WMARK(fifo_depth / 2 - 1) | TX_WMARK(fifo_depth / 2); - priv->drvsel = fdtdec_get_uint(blob, node, "drvsel", 3); - priv->smplsel = fdtdec_get_uint(blob, node, "smplsel", 0); + priv->drvsel = fdtdec_get_uint(blob, node, "drvsel", 0xf); + priv->smplsel = fdtdec_get_uint(blob, node, "smplsel", 0xf); host->priv = priv; return add_dwmci(host, host->bus_hz, 400000); -- 2.2.0 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot