diff options
Diffstat (limited to 'drivers/mmc/host/sunxi-mmc.c')
-rw-r--r-- | drivers/mmc/host/sunxi-mmc.c | 265 |
1 files changed, 193 insertions, 72 deletions
diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c index 2ee4c21ec55e..c0a5c676d0e8 100644 --- a/drivers/mmc/host/sunxi-mmc.c +++ b/drivers/mmc/host/sunxi-mmc.c @@ -72,6 +72,13 @@ #define SDXC_REG_CHDA (0x90) #define SDXC_REG_CBDA (0x94) +/* New registers introduced in A64 */ +#define SDXC_REG_A12A 0x058 /* SMC Auto Command 12 Register */ +#define SDXC_REG_SD_NTSR 0x05C /* SMC New Timing Set Register */ +#define SDXC_REG_DRV_DL 0x140 /* Drive Delay Control Register */ +#define SDXC_REG_SAMP_DL_REG 0x144 /* SMC sample delay control */ +#define SDXC_REG_DS_DL_REG 0x148 /* SMC data strobe delay control */ + #define mmc_readl(host, reg) \ readl((host)->reg_base + SDXC_##reg) #define mmc_writel(host, reg, value) \ @@ -217,21 +224,41 @@ #define SDXC_CLK_50M_DDR 3 #define SDXC_CLK_50M_DDR_8BIT 4 +#define SDXC_2X_TIMING_MODE BIT(31) + +#define SDXC_CAL_START BIT(15) +#define SDXC_CAL_DONE BIT(14) +#define SDXC_CAL_DL_SHIFT 8 +#define SDXC_CAL_DL_SW_EN BIT(7) +#define SDXC_CAL_DL_SW_SHIFT 0 +#define SDXC_CAL_DL_MASK 0x3f + +#define SDXC_CAL_TIMEOUT 3 /* in seconds, 3s is enough*/ + struct sunxi_mmc_clk_delay { u32 output; u32 sample; }; struct sunxi_idma_des { - u32 config; - u32 buf_size; - u32 buf_addr_ptr1; - u32 buf_addr_ptr2; + __le32 config; + __le32 buf_size; + __le32 buf_addr_ptr1; + __le32 buf_addr_ptr2; +}; + +struct sunxi_mmc_cfg { + u32 idma_des_size_bits; + const struct sunxi_mmc_clk_delay *clk_delays; + + /* does the IP block support autocalibration? */ + bool can_calibrate; }; struct sunxi_mmc_host { struct mmc_host *mmc; struct reset_control *reset; + const struct sunxi_mmc_cfg *cfg; /* IO mapping base */ void __iomem *reg_base; @@ -241,7 +268,6 @@ struct sunxi_mmc_host { struct clk *clk_mmc; struct clk *clk_sample; struct clk *clk_output; - const struct sunxi_mmc_clk_delay *clk_delays; /* irq */ spinlock_t lock; @@ -250,7 +276,6 @@ struct sunxi_mmc_host { u32 sdio_imask; /* dma */ - u32 idma_des_size_bits; dma_addr_t sg_dma; void *sg_cpu; bool wait_dma; @@ -322,25 +347,28 @@ static void sunxi_mmc_init_idma_des(struct sunxi_mmc_host *host, { struct sunxi_idma_des *pdes = (struct sunxi_idma_des *)host->sg_cpu; dma_addr_t next_desc = host->sg_dma; - int i, max_len = (1 << host->idma_des_size_bits); + int i, max_len = (1 << host->cfg->idma_des_size_bits); for (i = 0; i < data->sg_len; i++) { - pdes[i].config = SDXC_IDMAC_DES0_CH | SDXC_IDMAC_DES0_OWN | - SDXC_IDMAC_DES0_DIC; + pdes[i].config = cpu_to_le32(SDXC_IDMAC_DES0_CH | + SDXC_IDMAC_DES0_OWN | + SDXC_IDMAC_DES0_DIC); if (data->sg[i].length == max_len) pdes[i].buf_size = 0; /* 0 == max_len */ else - pdes[i].buf_size = data->sg[i].length; + pdes[i].buf_size = cpu_to_le32(data->sg[i].length); next_desc += sizeof(struct sunxi_idma_des); - pdes[i].buf_addr_ptr1 = sg_dma_address(&data->sg[i]); - pdes[i].buf_addr_ptr2 = (u32)next_desc; + pdes[i].buf_addr_ptr1 = + cpu_to_le32(sg_dma_address(&data->sg[i])); + pdes[i].buf_addr_ptr2 = cpu_to_le32((u32)next_desc); } - pdes[0].config |= SDXC_IDMAC_DES0_FD; - pdes[i - 1].config |= SDXC_IDMAC_DES0_LD | SDXC_IDMAC_DES0_ER; - pdes[i - 1].config &= ~SDXC_IDMAC_DES0_DIC; + pdes[0].config |= cpu_to_le32(SDXC_IDMAC_DES0_FD); + pdes[i - 1].config |= cpu_to_le32(SDXC_IDMAC_DES0_LD | + SDXC_IDMAC_DES0_ER); + pdes[i - 1].config &= cpu_to_le32(~SDXC_IDMAC_DES0_DIC); pdes[i - 1].buf_addr_ptr2 = 0; /* @@ -653,11 +681,84 @@ static int sunxi_mmc_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en) return 0; } +static int sunxi_mmc_calibrate(struct sunxi_mmc_host *host, int reg_off) +{ + u32 reg = readl(host->reg_base + reg_off); + u32 delay; + unsigned long timeout; + + if (!host->cfg->can_calibrate) + return 0; + + reg &= ~(SDXC_CAL_DL_MASK << SDXC_CAL_DL_SW_SHIFT); + reg &= ~SDXC_CAL_DL_SW_EN; + + writel(reg | SDXC_CAL_START, host->reg_base + reg_off); + + dev_dbg(mmc_dev(host->mmc), "calibration started\n"); + + timeout = jiffies + HZ * SDXC_CAL_TIMEOUT; + + while (!((reg = readl(host->reg_base + reg_off)) & SDXC_CAL_DONE)) { + if (time_before(jiffies, timeout)) + cpu_relax(); + else { + reg &= ~SDXC_CAL_START; + writel(reg, host->reg_base + reg_off); + + return -ETIMEDOUT; + } + } + + delay = (reg >> SDXC_CAL_DL_SHIFT) & SDXC_CAL_DL_MASK; + + reg &= ~SDXC_CAL_START; + reg |= (delay << SDXC_CAL_DL_SW_SHIFT) | SDXC_CAL_DL_SW_EN; + + writel(reg, host->reg_base + reg_off); + + dev_dbg(mmc_dev(host->mmc), "calibration ended, reg is 0x%x\n", reg); + + return 0; +} + +static int sunxi_mmc_clk_set_phase(struct sunxi_mmc_host *host, + struct mmc_ios *ios, u32 rate) +{ + int index; + + if (!host->cfg->clk_delays) + return 0; + + /* determine delays */ + if (rate <= 400000) { + index = SDXC_CLK_400K; + } else if (rate <= 25000000) { + index = SDXC_CLK_25M; + } else if (rate <= 52000000) { + if (ios->timing != MMC_TIMING_UHS_DDR50 && + ios->timing != MMC_TIMING_MMC_DDR52) { + index = SDXC_CLK_50M; + } else if (ios->bus_width == MMC_BUS_WIDTH_8) { + index = SDXC_CLK_50M_DDR_8BIT; + } else { + index = SDXC_CLK_50M_DDR; + } + } else { + return -EINVAL; + } + + clk_set_phase(host->clk_sample, host->cfg->clk_delays[index].sample); + clk_set_phase(host->clk_output, host->cfg->clk_delays[index].output); + + return 0; +} + static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host, struct mmc_ios *ios) { - u32 rate, oclk_dly, rval, sclk_dly; - u32 clock = ios->clock; + long rate; + u32 rval, clock = ios->clock; int ret; /* 8 bit DDR requires a higher module clock */ @@ -666,13 +767,18 @@ static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host, clock <<= 1; rate = clk_round_rate(host->clk_mmc, clock); - dev_dbg(mmc_dev(host->mmc), "setting clk to %d, rounded %d\n", + if (rate < 0) { + dev_err(mmc_dev(host->mmc), "error rounding clk to %d: %ld\n", + clock, rate); + return rate; + } + dev_dbg(mmc_dev(host->mmc), "setting clk to %d, rounded %ld\n", clock, rate); /* setting clock rate */ ret = clk_set_rate(host->clk_mmc, rate); if (ret) { - dev_err(mmc_dev(host->mmc), "error setting clk to %d: %d\n", + dev_err(mmc_dev(host->mmc), "error setting clk to %ld: %d\n", rate, ret); return ret; } @@ -692,31 +798,15 @@ static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host, } mmc_writel(host, REG_CLKCR, rval); - /* determine delays */ - if (rate <= 400000) { - oclk_dly = host->clk_delays[SDXC_CLK_400K].output; - sclk_dly = host->clk_delays[SDXC_CLK_400K].sample; - } else if (rate <= 25000000) { - oclk_dly = host->clk_delays[SDXC_CLK_25M].output; - sclk_dly = host->clk_delays[SDXC_CLK_25M].sample; - } else if (rate <= 52000000) { - if (ios->timing != MMC_TIMING_UHS_DDR50 && - ios->timing != MMC_TIMING_MMC_DDR52) { - oclk_dly = host->clk_delays[SDXC_CLK_50M].output; - sclk_dly = host->clk_delays[SDXC_CLK_50M].sample; - } else if (ios->bus_width == MMC_BUS_WIDTH_8) { - oclk_dly = host->clk_delays[SDXC_CLK_50M_DDR_8BIT].output; - sclk_dly = host->clk_delays[SDXC_CLK_50M_DDR_8BIT].sample; - } else { - oclk_dly = host->clk_delays[SDXC_CLK_50M_DDR].output; - sclk_dly = host->clk_delays[SDXC_CLK_50M_DDR].sample; - } - } else { - return -EINVAL; - } + ret = sunxi_mmc_clk_set_phase(host, ios, rate); + if (ret) + return ret; + + ret = sunxi_mmc_calibrate(host, SDXC_REG_SAMP_DL_REG); + if (ret) + return ret; - clk_set_phase(host->clk_sample, sclk_dly); - clk_set_phase(host->clk_output, oclk_dly); + /* TODO: enable calibrate on sdc2 SDXC_REG_DS_DL_REG of A64 */ return sunxi_mmc_oclk_onoff(host, 1); } @@ -938,14 +1028,6 @@ static int sunxi_mmc_card_busy(struct mmc_host *mmc) return !!(mmc_readl(host, REG_STAS) & SDXC_CARD_DATA_BUSY); } -static const struct of_device_id sunxi_mmc_of_match[] = { - { .compatible = "allwinner,sun4i-a10-mmc", }, - { .compatible = "allwinner,sun5i-a13-mmc", }, - { .compatible = "allwinner,sun9i-a80-mmc", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match); - static struct mmc_host_ops sunxi_mmc_ops = { .request = sunxi_mmc_request, .set_ios = sunxi_mmc_set_ios, @@ -974,21 +1056,54 @@ static const struct sunxi_mmc_clk_delay sun9i_mmc_clk_delays[] = { [SDXC_CLK_50M_DDR_8BIT] = { .output = 72, .sample = 72 }, }; +static const struct sunxi_mmc_cfg sun4i_a10_cfg = { + .idma_des_size_bits = 13, + .clk_delays = NULL, + .can_calibrate = false, +}; + +static const struct sunxi_mmc_cfg sun5i_a13_cfg = { + .idma_des_size_bits = 16, + .clk_delays = NULL, + .can_calibrate = false, +}; + +static const struct sunxi_mmc_cfg sun7i_a20_cfg = { + .idma_des_size_bits = 16, + .clk_delays = sunxi_mmc_clk_delays, + .can_calibrate = false, +}; + +static const struct sunxi_mmc_cfg sun9i_a80_cfg = { + .idma_des_size_bits = 16, + .clk_delays = sun9i_mmc_clk_delays, + .can_calibrate = false, +}; + +static const struct sunxi_mmc_cfg sun50i_a64_cfg = { + .idma_des_size_bits = 16, + .clk_delays = NULL, + .can_calibrate = true, +}; + +static const struct of_device_id sunxi_mmc_of_match[] = { + { .compatible = "allwinner,sun4i-a10-mmc", .data = &sun4i_a10_cfg }, + { .compatible = "allwinner,sun5i-a13-mmc", .data = &sun5i_a13_cfg }, + { .compatible = "allwinner,sun7i-a20-mmc", .data = &sun7i_a20_cfg }, + { .compatible = "allwinner,sun9i-a80-mmc", .data = &sun9i_a80_cfg }, + { .compatible = "allwinner,sun50i-a64-mmc", .data = &sun50i_a64_cfg }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match); + static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host, struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; int ret; - if (of_device_is_compatible(np, "allwinner,sun4i-a10-mmc")) - host->idma_des_size_bits = 13; - else - host->idma_des_size_bits = 16; - - if (of_device_is_compatible(np, "allwinner,sun9i-a80-mmc")) - host->clk_delays = sun9i_mmc_clk_delays; - else - host->clk_delays = sunxi_mmc_clk_delays; + host->cfg = of_device_get_match_data(&pdev->dev); + if (!host->cfg) + return -EINVAL; ret = mmc_regulator_get_supply(host->mmc); if (ret) { @@ -1014,16 +1129,18 @@ static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host, return PTR_ERR(host->clk_mmc); } - host->clk_output = devm_clk_get(&pdev->dev, "output"); - if (IS_ERR(host->clk_output)) { - dev_err(&pdev->dev, "Could not get output clock\n"); - return PTR_ERR(host->clk_output); - } + if (host->cfg->clk_delays) { + host->clk_output = devm_clk_get(&pdev->dev, "output"); + if (IS_ERR(host->clk_output)) { + dev_err(&pdev->dev, "Could not get output clock\n"); + return PTR_ERR(host->clk_output); + } - host->clk_sample = devm_clk_get(&pdev->dev, "sample"); - if (IS_ERR(host->clk_sample)) { - dev_err(&pdev->dev, "Could not get sample clock\n"); - return PTR_ERR(host->clk_sample); + host->clk_sample = devm_clk_get(&pdev->dev, "sample"); + if (IS_ERR(host->clk_sample)) { + dev_err(&pdev->dev, "Could not get sample clock\n"); + return PTR_ERR(host->clk_sample); + } } host->reset = devm_reset_control_get_optional(&pdev->dev, "ahb"); @@ -1120,15 +1237,17 @@ static int sunxi_mmc_probe(struct platform_device *pdev) mmc->max_blk_count = 8192; mmc->max_blk_size = 4096; mmc->max_segs = PAGE_SIZE / sizeof(struct sunxi_idma_des); - mmc->max_seg_size = (1 << host->idma_des_size_bits); + mmc->max_seg_size = (1 << host->cfg->idma_des_size_bits); mmc->max_req_size = mmc->max_seg_size * mmc->max_segs; /* 400kHz ~ 52MHz */ mmc->f_min = 400000; mmc->f_max = 52000000; mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | - MMC_CAP_1_8V_DDR | MMC_CAP_ERASE | MMC_CAP_SDIO_IRQ; + if (host->cfg->clk_delays) + mmc->caps |= MMC_CAP_1_8V_DDR; + ret = mmc_of_parse(mmc); if (ret) goto error_free_dma; @@ -1160,6 +1279,8 @@ static int sunxi_mmc_remove(struct platform_device *pdev) if (!IS_ERR(host->reset)) reset_control_assert(host->reset); + clk_disable_unprepare(host->clk_sample); + clk_disable_unprepare(host->clk_output); clk_disable_unprepare(host->clk_mmc); clk_disable_unprepare(host->clk_ahb); |