]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/mmc/host/meson-gx-mmc.c
Merge tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm
[karo-tx-linux.git] / drivers / mmc / host / meson-gx-mmc.c
index 5a959783304bc6fd97cd8edf9cdca6614413cc18..1842ed341af10e38902f1178f409d645b7301679 100644 (file)
 #include <linux/clk-provider.h>
 #include <linux/regulator/consumer.h>
 #include <linux/interrupt.h>
+#include <linux/bitfield.h>
 
 #define DRIVER_NAME "meson-gx-mmc"
 
 #define SD_EMMC_CLOCK 0x0
-#define   CLK_DIV_SHIFT 0
-#define   CLK_DIV_WIDTH 6
-#define   CLK_DIV_MASK 0x3f
+#define   CLK_DIV_MASK GENMASK(5, 0)
 #define   CLK_DIV_MAX 63
-#define   CLK_SRC_SHIFT 6
-#define   CLK_SRC_WIDTH 2
-#define   CLK_SRC_MASK 0x3
+#define   CLK_SRC_MASK GENMASK(7, 6)
 #define   CLK_SRC_XTAL 0   /* external crystal */
 #define   CLK_SRC_XTAL_RATE 24000000
 #define   CLK_SRC_PLL 1    /* FCLK_DIV2 */
 #define   CLK_SRC_PLL_RATE 1000000000
-#define   CLK_PHASE_SHIFT 8
-#define   CLK_PHASE_MASK 0x3
+#define   CLK_CORE_PHASE_MASK GENMASK(9, 8)
+#define   CLK_TX_PHASE_MASK GENMASK(11, 10)
+#define   CLK_RX_PHASE_MASK GENMASK(13, 12)
 #define   CLK_PHASE_0 0
 #define   CLK_PHASE_90 1
 #define   CLK_PHASE_180 2
 #define SD_EMMC_START 0x40
 #define   START_DESC_INIT BIT(0)
 #define   START_DESC_BUSY BIT(1)
-#define   START_DESC_ADDR_SHIFT 2
-#define   START_DESC_ADDR_MASK (~0x3)
+#define   START_DESC_ADDR_MASK GENMASK(31, 2)
 
 #define SD_EMMC_CFG 0x44
-#define   CFG_BUS_WIDTH_SHIFT 0
-#define   CFG_BUS_WIDTH_MASK 0x3
+#define   CFG_BUS_WIDTH_MASK GENMASK(1, 0)
 #define   CFG_BUS_WIDTH_1 0x0
 #define   CFG_BUS_WIDTH_4 0x1
 #define   CFG_BUS_WIDTH_8 0x2
 #define   CFG_DDR BIT(2)
-#define   CFG_BLK_LEN_SHIFT 4
-#define   CFG_BLK_LEN_MASK 0xf
-#define   CFG_RESP_TIMEOUT_SHIFT 8
-#define   CFG_RESP_TIMEOUT_MASK 0xf
-#define   CFG_RC_CC_SHIFT 12
-#define   CFG_RC_CC_MASK 0xf
+#define   CFG_BLK_LEN_MASK GENMASK(7, 4)
+#define   CFG_RESP_TIMEOUT_MASK GENMASK(11, 8)
+#define   CFG_RC_CC_MASK GENMASK(15, 12)
 #define   CFG_STOP_CLOCK BIT(22)
 #define   CFG_CLK_ALWAYS_ON BIT(18)
 #define   CFG_CHK_DS BIT(20)
@@ -90,9 +83,8 @@
 #define   STATUS_BUSY BIT(31)
 
 #define SD_EMMC_IRQ_EN 0x4c
-#define   IRQ_EN_MASK 0x3fff
-#define   IRQ_RXD_ERR_SHIFT 0
-#define   IRQ_RXD_ERR_MASK 0xff
+#define   IRQ_EN_MASK GENMASK(13, 0)
+#define   IRQ_RXD_ERR_MASK GENMASK(7, 0)
 #define   IRQ_TXD_ERR BIT(8)
 #define   IRQ_DESC_ERR BIT(9)
 #define   IRQ_RESP_ERR BIT(10)
 
 #define SD_EMMC_CFG_BLK_SIZE 512 /* internal buffer max: 512 bytes */
 #define SD_EMMC_CFG_RESP_TIMEOUT 256 /* in clock cycles */
+#define SD_EMMC_CMD_TIMEOUT 1024 /* in ms */
+#define SD_EMMC_CMD_TIMEOUT_DATA 4096 /* in ms */
 #define SD_EMMC_CFG_CMD_GAP 16 /* in clock cycles */
+#define SD_EMMC_DESC_BUF_LEN PAGE_SIZE
+
+#define SD_EMMC_PRE_REQ_DONE BIT(0)
+#define SD_EMMC_DESC_CHAIN_MODE BIT(1)
+
 #define MUX_CLK_NUM_PARENTS 2
 
+struct meson_tuning_params {
+       u8 core_phase;
+       u8 tx_phase;
+       u8 rx_phase;
+};
+
+struct sd_emmc_desc {
+       u32 cmd_cfg;
+       u32 cmd_arg;
+       u32 cmd_data;
+       u32 cmd_resp;
+};
+
 struct meson_host {
        struct  device          *dev;
        struct  mmc_host        *mmc;
-       struct  mmc_request     *mrq;
        struct  mmc_command     *cmd;
 
        spinlock_t lock;
        void __iomem *regs;
-       int irq;
-       u32 ocr_mask;
        struct clk *core_clk;
        struct clk_mux mux;
        struct clk *mux_clk;
-       struct clk *mux_parent[MUX_CLK_NUM_PARENTS];
        unsigned long current_clock;
 
        struct clk_divider cfg_div;
@@ -141,23 +149,18 @@ struct meson_host {
        unsigned int bounce_buf_size;
        void *bounce_buf;
        dma_addr_t bounce_dma_addr;
+       struct sd_emmc_desc *descs;
+       dma_addr_t descs_dma_addr;
 
+       struct meson_tuning_params tp;
        bool vqmmc_enabled;
 };
 
-struct sd_emmc_desc {
-       u32 cmd_cfg;
-       u32 cmd_arg;
-       u32 cmd_data;
-       u32 cmd_resp;
-};
-#define CMD_CFG_LENGTH_SHIFT 0
-#define CMD_CFG_LENGTH_MASK 0x1ff
+#define CMD_CFG_LENGTH_MASK GENMASK(8, 0)
 #define CMD_CFG_BLOCK_MODE BIT(9)
 #define CMD_CFG_R1B BIT(10)
 #define CMD_CFG_END_OF_CHAIN BIT(11)
-#define CMD_CFG_TIMEOUT_SHIFT 12
-#define CMD_CFG_TIMEOUT_MASK 0xf
+#define CMD_CFG_TIMEOUT_MASK GENMASK(15, 12)
 #define CMD_CFG_NO_RESP BIT(16)
 #define CMD_CFG_NO_CMD BIT(17)
 #define CMD_CFG_DATA_IO BIT(18)
@@ -166,17 +169,99 @@ struct sd_emmc_desc {
 #define CMD_CFG_RESP_128 BIT(21)
 #define CMD_CFG_RESP_NUM BIT(22)
 #define CMD_CFG_DATA_NUM BIT(23)
-#define CMD_CFG_CMD_INDEX_SHIFT 24
-#define CMD_CFG_CMD_INDEX_MASK 0x3f
+#define CMD_CFG_CMD_INDEX_MASK GENMASK(29, 24)
 #define CMD_CFG_ERROR BIT(30)
 #define CMD_CFG_OWNER BIT(31)
 
-#define CMD_DATA_MASK (~0x3)
+#define CMD_DATA_MASK GENMASK(31, 2)
 #define CMD_DATA_BIG_ENDIAN BIT(1)
 #define CMD_DATA_SRAM BIT(0)
-#define CMD_RESP_MASK (~0x1)
+#define CMD_RESP_MASK GENMASK(31, 1)
 #define CMD_RESP_SRAM BIT(0)
 
+static unsigned int meson_mmc_get_timeout_msecs(struct mmc_data *data)
+{
+       unsigned int timeout = data->timeout_ns / NSEC_PER_MSEC;
+
+       if (!timeout)
+               return SD_EMMC_CMD_TIMEOUT_DATA;
+
+       timeout = roundup_pow_of_two(timeout);
+
+       return min(timeout, 32768U); /* max. 2^15 ms */
+}
+
+static struct mmc_command *meson_mmc_get_next_command(struct mmc_command *cmd)
+{
+       if (cmd->opcode == MMC_SET_BLOCK_COUNT && !cmd->error)
+               return cmd->mrq->cmd;
+       else if (mmc_op_multi(cmd->opcode) &&
+                (!cmd->mrq->sbc || cmd->error || cmd->data->error))
+               return cmd->mrq->stop;
+       else
+               return NULL;
+}
+
+static void meson_mmc_get_transfer_mode(struct mmc_host *mmc,
+                                       struct mmc_request *mrq)
+{
+       struct mmc_data *data = mrq->data;
+       struct scatterlist *sg;
+       int i;
+       bool use_desc_chain_mode = true;
+
+       for_each_sg(data->sg, sg, data->sg_len, i)
+               /* check for 8 byte alignment */
+               if (sg->offset & 7) {
+                       WARN_ONCE(1, "unaligned scatterlist buffer\n");
+                       use_desc_chain_mode = false;
+                       break;
+               }
+
+       if (use_desc_chain_mode)
+               data->host_cookie |= SD_EMMC_DESC_CHAIN_MODE;
+}
+
+static inline bool meson_mmc_desc_chain_mode(const struct mmc_data *data)
+{
+       return data->host_cookie & SD_EMMC_DESC_CHAIN_MODE;
+}
+
+static inline bool meson_mmc_bounce_buf_read(const struct mmc_data *data)
+{
+       return data && data->flags & MMC_DATA_READ &&
+              !meson_mmc_desc_chain_mode(data);
+}
+
+static void meson_mmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+       struct mmc_data *data = mrq->data;
+
+       if (!data)
+               return;
+
+       meson_mmc_get_transfer_mode(mmc, mrq);
+       data->host_cookie |= SD_EMMC_PRE_REQ_DONE;
+
+       if (!meson_mmc_desc_chain_mode(data))
+               return;
+
+       data->sg_count = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len,
+                                   mmc_get_dma_dir(data));
+       if (!data->sg_count)
+               dev_err(mmc_dev(mmc), "dma_map_sg failed");
+}
+
+static void meson_mmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
+                              int err)
+{
+       struct mmc_data *data = mrq->data;
+
+       if (data && meson_mmc_desc_chain_mode(data) && data->sg_count)
+               dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len,
+                            mmc_get_dma_dir(data));
+}
+
 static int meson_mmc_clk_set(struct meson_host *host, unsigned long clk_rate)
 {
        struct mmc_host *mmc = host->mmc;
@@ -244,26 +329,23 @@ static int meson_mmc_clk_init(struct meson_host *host)
        char clk_name[32];
        int i, ret = 0;
        const char *mux_parent_names[MUX_CLK_NUM_PARENTS];
-       unsigned int mux_parent_count = 0;
        const char *clk_div_parents[1];
        u32 clk_reg, cfg;
 
        /* get the mux parents */
        for (i = 0; i < MUX_CLK_NUM_PARENTS; i++) {
+               struct clk *clk;
                char name[16];
 
                snprintf(name, sizeof(name), "clkin%d", i);
-               host->mux_parent[i] = devm_clk_get(host->dev, name);
-               if (IS_ERR(host->mux_parent[i])) {
-                       ret = PTR_ERR(host->mux_parent[i]);
-                       if (PTR_ERR(host->mux_parent[i]) != -EPROBE_DEFER)
+               clk = devm_clk_get(host->dev, name);
+               if (IS_ERR(clk)) {
+                       if (clk != ERR_PTR(-EPROBE_DEFER))
                                dev_err(host->dev, "Missing clock %s\n", name);
-                       host->mux_parent[i] = NULL;
-                       return ret;
+                       return PTR_ERR(clk);
                }
 
-               mux_parent_names[i] = __clk_get_name(host->mux_parent[i]);
-               mux_parent_count++;
+               mux_parent_names[i] = __clk_get_name(clk);
        }
 
        /* create the mux */
@@ -272,10 +354,9 @@ static int meson_mmc_clk_init(struct meson_host *host)
        init.ops = &clk_mux_ops;
        init.flags = 0;
        init.parent_names = mux_parent_names;
-       init.num_parents = mux_parent_count;
-
+       init.num_parents = MUX_CLK_NUM_PARENTS;
        host->mux.reg = host->regs + SD_EMMC_CLOCK;
-       host->mux.shift = CLK_SRC_SHIFT;
+       host->mux.shift = __bf_shf(CLK_SRC_MASK);
        host->mux.mask = CLK_SRC_MASK;
        host->mux.flags = 0;
        host->mux.table = NULL;
@@ -287,7 +368,7 @@ static int meson_mmc_clk_init(struct meson_host *host)
 
        /* create the divider */
        snprintf(clk_name, sizeof(clk_name), "%s#div", dev_name(host->dev));
-       init.name = devm_kstrdup(host->dev, clk_name, GFP_KERNEL);
+       init.name = clk_name;
        init.ops = &clk_divider_ops;
        init.flags = CLK_SET_RATE_PARENT;
        clk_div_parents[0] = __clk_get_name(host->mux_clk);
@@ -295,8 +376,8 @@ static int meson_mmc_clk_init(struct meson_host *host)
        init.num_parents = ARRAY_SIZE(clk_div_parents);
 
        host->cfg_div.reg = host->regs + SD_EMMC_CLOCK;
-       host->cfg_div.shift = CLK_DIV_SHIFT;
-       host->cfg_div.width = CLK_DIV_WIDTH;
+       host->cfg_div.shift = __bf_shf(CLK_DIV_MASK);
+       host->cfg_div.width = __builtin_popcountl(CLK_DIV_MASK);
        host->cfg_div.hw.init = &init;
        host->cfg_div.flags = CLK_DIVIDER_ONE_BASED |
                CLK_DIVIDER_ROUND_CLOSEST | CLK_DIVIDER_ALLOW_ZERO;
@@ -307,9 +388,11 @@ static int meson_mmc_clk_init(struct meson_host *host)
 
        /* init SD_EMMC_CLOCK to sane defaults w/min clock rate */
        clk_reg = 0;
-       clk_reg |= CLK_PHASE_180 << CLK_PHASE_SHIFT;
-       clk_reg |= CLK_SRC_XTAL << CLK_SRC_SHIFT;
-       clk_reg |= CLK_DIV_MAX << CLK_DIV_SHIFT;
+       clk_reg |= FIELD_PREP(CLK_CORE_PHASE_MASK, host->tp.core_phase);
+       clk_reg |= FIELD_PREP(CLK_TX_PHASE_MASK, host->tp.tx_phase);
+       clk_reg |= FIELD_PREP(CLK_RX_PHASE_MASK, host->tp.rx_phase);
+       clk_reg |= FIELD_PREP(CLK_SRC_MASK, CLK_SRC_XTAL);
+       clk_reg |= FIELD_PREP(CLK_DIV_MASK, CLK_DIV_MAX);
        clk_reg &= ~CLK_ALWAYS_ON;
        writel(clk_reg, host->regs + SD_EMMC_CLOCK);
 
@@ -327,12 +410,37 @@ static int meson_mmc_clk_init(struct meson_host *host)
        host->mmc->f_min = clk_round_rate(host->cfg_div_clk, 400000);
 
        ret = meson_mmc_clk_set(host, host->mmc->f_min);
-       if (!ret)
+       if (ret)
                clk_disable_unprepare(host->cfg_div_clk);
 
        return ret;
 }
 
+static void meson_mmc_set_tuning_params(struct mmc_host *mmc)
+{
+       struct meson_host *host = mmc_priv(mmc);
+       u32 regval;
+
+       /* stop clock */
+       regval = readl(host->regs + SD_EMMC_CFG);
+       regval |= CFG_STOP_CLOCK;
+       writel(regval, host->regs + SD_EMMC_CFG);
+
+       regval = readl(host->regs + SD_EMMC_CLOCK);
+       regval &= ~CLK_CORE_PHASE_MASK;
+       regval |= FIELD_PREP(CLK_CORE_PHASE_MASK, host->tp.core_phase);
+       regval &= ~CLK_TX_PHASE_MASK;
+       regval |= FIELD_PREP(CLK_TX_PHASE_MASK, host->tp.tx_phase);
+       regval &= ~CLK_RX_PHASE_MASK;
+       regval |= FIELD_PREP(CLK_RX_PHASE_MASK, host->tp.rx_phase);
+       writel(regval, host->regs + SD_EMMC_CLOCK);
+
+       /* start clock */
+       regval = readl(host->regs + SD_EMMC_CFG);
+       regval &= ~CFG_STOP_CLOCK;
+       writel(regval, host->regs + SD_EMMC_CFG);
+}
+
 static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
        struct meson_host *host = mmc_priv(mmc);
@@ -397,17 +505,8 @@ static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        val = readl(host->regs + SD_EMMC_CFG);
        orig = val;
 
-       val &= ~(CFG_BUS_WIDTH_MASK << CFG_BUS_WIDTH_SHIFT);
-       val |= bus_width << CFG_BUS_WIDTH_SHIFT;
-
-       val &= ~(CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT);
-       val |= ilog2(SD_EMMC_CFG_BLK_SIZE) << CFG_BLK_LEN_SHIFT;
-
-       val &= ~(CFG_RESP_TIMEOUT_MASK << CFG_RESP_TIMEOUT_SHIFT);
-       val |= ilog2(SD_EMMC_CFG_RESP_TIMEOUT) << CFG_RESP_TIMEOUT_SHIFT;
-
-       val &= ~(CFG_RC_CC_MASK << CFG_RC_CC_SHIFT);
-       val |= ilog2(SD_EMMC_CFG_CMD_GAP) << CFG_RC_CC_SHIFT;
+       val &= ~CFG_BUS_WIDTH_MASK;
+       val |= FIELD_PREP(CFG_BUS_WIDTH_MASK, bus_width);
 
        val &= ~CFG_DDR;
        if (ios->timing == MMC_TIMING_UHS_DDR50 ||
@@ -419,149 +518,189 @@ static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        if (ios->timing == MMC_TIMING_MMC_HS400)
                val |= CFG_CHK_DS;
 
-       writel(val, host->regs + SD_EMMC_CFG);
-
-       if (val != orig)
+       if (val != orig) {
+               writel(val, host->regs + SD_EMMC_CFG);
                dev_dbg(host->dev, "%s: SD_EMMC_CFG: 0x%08x -> 0x%08x\n",
                        __func__, orig, val);
+       }
 }
 
-static int meson_mmc_request_done(struct mmc_host *mmc, struct mmc_request *mrq)
+static void meson_mmc_request_done(struct mmc_host *mmc,
+                                  struct mmc_request *mrq)
 {
        struct meson_host *host = mmc_priv(mmc);
 
-       WARN_ON(host->mrq != mrq);
-
-       host->mrq = NULL;
        host->cmd = NULL;
        mmc_request_done(host->mmc, mrq);
-
-       return 0;
 }
 
-static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd)
+static void meson_mmc_set_blksz(struct mmc_host *mmc, unsigned int blksz)
 {
        struct meson_host *host = mmc_priv(mmc);
-       struct sd_emmc_desc *desc, desc_tmp;
-       u32 cfg;
-       u8 blk_len, cmd_cfg_timeout;
-       unsigned int xfer_bytes = 0;
+       u32 cfg, blksz_old;
 
-       /* Setup descriptors */
-       dma_rmb();
-       desc = &desc_tmp;
-       memset(desc, 0, sizeof(struct sd_emmc_desc));
+       cfg = readl(host->regs + SD_EMMC_CFG);
+       blksz_old = FIELD_GET(CFG_BLK_LEN_MASK, cfg);
+
+       if (!is_power_of_2(blksz))
+               dev_err(host->dev, "blksz %u is not a power of 2\n", blksz);
+
+       blksz = ilog2(blksz);
+
+       /* check if block-size matches, if not update */
+       if (blksz == blksz_old)
+               return;
 
-       desc->cmd_cfg |= (cmd->opcode & CMD_CFG_CMD_INDEX_MASK) <<
-               CMD_CFG_CMD_INDEX_SHIFT;
-       desc->cmd_cfg |= CMD_CFG_OWNER;  /* owned by CPU */
-       desc->cmd_arg = cmd->arg;
+       dev_dbg(host->dev, "%s: update blk_len %d -> %d\n", __func__,
+               blksz_old, blksz);
 
-       /* Response */
+       cfg &= ~CFG_BLK_LEN_MASK;
+       cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, blksz);
+       writel(cfg, host->regs + SD_EMMC_CFG);
+}
+
+static void meson_mmc_set_response_bits(struct mmc_command *cmd, u32 *cmd_cfg)
+{
        if (cmd->flags & MMC_RSP_PRESENT) {
-               desc->cmd_cfg &= ~CMD_CFG_NO_RESP;
                if (cmd->flags & MMC_RSP_136)
-                       desc->cmd_cfg |= CMD_CFG_RESP_128;
-               desc->cmd_cfg |= CMD_CFG_RESP_NUM;
-               desc->cmd_resp = 0;
+                       *cmd_cfg |= CMD_CFG_RESP_128;
+               *cmd_cfg |= CMD_CFG_RESP_NUM;
 
                if (!(cmd->flags & MMC_RSP_CRC))
-                       desc->cmd_cfg |= CMD_CFG_RESP_NOCRC;
+                       *cmd_cfg |= CMD_CFG_RESP_NOCRC;
 
                if (cmd->flags & MMC_RSP_BUSY)
-                       desc->cmd_cfg |= CMD_CFG_R1B;
+                       *cmd_cfg |= CMD_CFG_R1B;
        } else {
-               desc->cmd_cfg |= CMD_CFG_NO_RESP;
+               *cmd_cfg |= CMD_CFG_NO_RESP;
        }
+}
+
+static void meson_mmc_desc_chain_transfer(struct mmc_host *mmc, u32 cmd_cfg)
+{
+       struct meson_host *host = mmc_priv(mmc);
+       struct sd_emmc_desc *desc = host->descs;
+       struct mmc_data *data = host->cmd->data;
+       struct scatterlist *sg;
+       u32 start;
+       int i;
+
+       if (data->flags & MMC_DATA_WRITE)
+               cmd_cfg |= CMD_CFG_DATA_WR;
+
+       if (data->blocks > 1) {
+               cmd_cfg |= CMD_CFG_BLOCK_MODE;
+               meson_mmc_set_blksz(mmc, data->blksz);
+       }
+
+       for_each_sg(data->sg, sg, data->sg_count, i) {
+               unsigned int len = sg_dma_len(sg);
+
+               if (data->blocks > 1)
+                       len /= data->blksz;
+
+               desc[i].cmd_cfg = cmd_cfg;
+               desc[i].cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, len);
+               if (i > 0)
+                       desc[i].cmd_cfg |= CMD_CFG_NO_CMD;
+               desc[i].cmd_arg = host->cmd->arg;
+               desc[i].cmd_resp = 0;
+               desc[i].cmd_data = sg_dma_address(sg);
+       }
+       desc[data->sg_count - 1].cmd_cfg |= CMD_CFG_END_OF_CHAIN;
+
+       dma_wmb(); /* ensure descriptor is written before kicked */
+       start = host->descs_dma_addr | START_DESC_BUSY;
+       writel(start, host->regs + SD_EMMC_START);
+}
+
+static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+       struct meson_host *host = mmc_priv(mmc);
+       struct mmc_data *data = cmd->data;
+       u32 cmd_cfg = 0, cmd_data = 0;
+       unsigned int xfer_bytes = 0;
+
+       /* Setup descriptors */
+       dma_rmb();
+
+       host->cmd = cmd;
+
+       cmd_cfg |= FIELD_PREP(CMD_CFG_CMD_INDEX_MASK, cmd->opcode);
+       cmd_cfg |= CMD_CFG_OWNER;  /* owned by CPU */
+
+       meson_mmc_set_response_bits(cmd, &cmd_cfg);
 
        /* data? */
-       if (cmd->data) {
-               desc->cmd_cfg |= CMD_CFG_DATA_IO;
-               if (cmd->data->blocks > 1) {
-                       desc->cmd_cfg |= CMD_CFG_BLOCK_MODE;
-                       desc->cmd_cfg |=
-                               (cmd->data->blocks & CMD_CFG_LENGTH_MASK) <<
-                               CMD_CFG_LENGTH_SHIFT;
-
-                       /* check if block-size matches, if not update */
-                       cfg = readl(host->regs + SD_EMMC_CFG);
-                       blk_len = cfg & (CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT);
-                       blk_len >>= CFG_BLK_LEN_SHIFT;
-                       if (blk_len != ilog2(cmd->data->blksz)) {
-                               dev_dbg(host->dev, "%s: update blk_len %d -> %d\n",
-                                       __func__, blk_len,
-                                       ilog2(cmd->data->blksz));
-                               blk_len = ilog2(cmd->data->blksz);
-                               cfg &= ~(CFG_BLK_LEN_MASK << CFG_BLK_LEN_SHIFT);
-                               cfg |= blk_len << CFG_BLK_LEN_SHIFT;
-                               writel(cfg, host->regs + SD_EMMC_CFG);
-                       }
+       if (data) {
+               data->bytes_xfered = 0;
+               cmd_cfg |= CMD_CFG_DATA_IO;
+               cmd_cfg |= FIELD_PREP(CMD_CFG_TIMEOUT_MASK,
+                                     ilog2(meson_mmc_get_timeout_msecs(data)));
+
+               if (meson_mmc_desc_chain_mode(data)) {
+                       meson_mmc_desc_chain_transfer(mmc, cmd_cfg);
+                       return;
+               }
+
+               if (data->blocks > 1) {
+                       cmd_cfg |= CMD_CFG_BLOCK_MODE;
+                       cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK,
+                                             data->blocks);
+                       meson_mmc_set_blksz(mmc, data->blksz);
                } else {
-                       desc->cmd_cfg &= ~CMD_CFG_BLOCK_MODE;
-                       desc->cmd_cfg |=
-                               (cmd->data->blksz & CMD_CFG_LENGTH_MASK) <<
-                               CMD_CFG_LENGTH_SHIFT;
+                       cmd_cfg |= FIELD_PREP(CMD_CFG_LENGTH_MASK, data->blksz);
                }
 
-               cmd->data->bytes_xfered = 0;
-               xfer_bytes = cmd->data->blksz * cmd->data->blocks;
-               if (cmd->data->flags & MMC_DATA_WRITE) {
-                       desc->cmd_cfg |= CMD_CFG_DATA_WR;
+               xfer_bytes = data->blksz * data->blocks;
+               if (data->flags & MMC_DATA_WRITE) {
+                       cmd_cfg |= CMD_CFG_DATA_WR;
                        WARN_ON(xfer_bytes > host->bounce_buf_size);
-                       sg_copy_to_buffer(cmd->data->sg, cmd->data->sg_len,
+                       sg_copy_to_buffer(data->sg, data->sg_len,
                                          host->bounce_buf, xfer_bytes);
-                       cmd->data->bytes_xfered = xfer_bytes;
                        dma_wmb();
-               } else {
-                       desc->cmd_cfg &= ~CMD_CFG_DATA_WR;
-               }
-
-               if (xfer_bytes > 0) {
-                       desc->cmd_cfg &= ~CMD_CFG_DATA_NUM;
-                       desc->cmd_data = host->bounce_dma_addr & CMD_DATA_MASK;
-               } else {
-                       /* write data to data_addr */
-                       desc->cmd_cfg |= CMD_CFG_DATA_NUM;
-                       desc->cmd_data = 0;
                }
 
-               cmd_cfg_timeout = 12;
+               cmd_data = host->bounce_dma_addr & CMD_DATA_MASK;
        } else {
-               desc->cmd_cfg &= ~CMD_CFG_DATA_IO;
-               cmd_cfg_timeout = 10;
+               cmd_cfg |= FIELD_PREP(CMD_CFG_TIMEOUT_MASK,
+                                     ilog2(SD_EMMC_CMD_TIMEOUT));
        }
-       desc->cmd_cfg |= (cmd_cfg_timeout & CMD_CFG_TIMEOUT_MASK) <<
-               CMD_CFG_TIMEOUT_SHIFT;
-
-       host->cmd = cmd;
 
        /* Last descriptor */
-       desc->cmd_cfg |= CMD_CFG_END_OF_CHAIN;
-       writel(desc->cmd_cfg, host->regs + SD_EMMC_CMD_CFG);
-       writel(desc->cmd_data, host->regs + SD_EMMC_CMD_DAT);
-       writel(desc->cmd_resp, host->regs + SD_EMMC_CMD_RSP);
+       cmd_cfg |= CMD_CFG_END_OF_CHAIN;
+       writel(cmd_cfg, host->regs + SD_EMMC_CMD_CFG);
+       writel(cmd_data, host->regs + SD_EMMC_CMD_DAT);
+       writel(0, host->regs + SD_EMMC_CMD_RSP);
        wmb(); /* ensure descriptor is written before kicked */
-       writel(desc->cmd_arg, host->regs + SD_EMMC_CMD_ARG);
+       writel(cmd->arg, host->regs + SD_EMMC_CMD_ARG);
 }
 
 static void meson_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
 {
        struct meson_host *host = mmc_priv(mmc);
+       bool needs_pre_post_req = mrq->data &&
+                       !(mrq->data->host_cookie & SD_EMMC_PRE_REQ_DONE);
 
-       WARN_ON(host->mrq != NULL);
+       if (needs_pre_post_req) {
+               meson_mmc_get_transfer_mode(mmc, mrq);
+               if (!meson_mmc_desc_chain_mode(mrq->data))
+                       needs_pre_post_req = false;
+       }
+
+       if (needs_pre_post_req)
+               meson_mmc_pre_req(mmc, mrq);
 
        /* Stop execution */
        writel(0, host->regs + SD_EMMC_START);
 
-       host->mrq = mrq;
+       meson_mmc_start_cmd(mmc, mrq->sbc ?: mrq->cmd);
 
-       if (mrq->sbc)
-               meson_mmc_start_cmd(mmc, mrq->sbc);
-       else
-               meson_mmc_start_cmd(mmc, mrq->cmd);
+       if (needs_pre_post_req)
+               meson_mmc_post_req(mmc, mrq, 0);
 }
 
-static int meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd)
+static void meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd)
 {
        struct meson_host *host = mmc_priv(mmc);
 
@@ -573,15 +712,13 @@ static int meson_mmc_read_resp(struct mmc_host *mmc, struct mmc_command *cmd)
        } else if (cmd->flags & MMC_RSP_PRESENT) {
                cmd->resp[0] = readl(host->regs + SD_EMMC_CMD_RSP);
        }
-
-       return 0;
 }
 
 static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
 {
        struct meson_host *host = dev_id;
-       struct mmc_request *mrq;
        struct mmc_command *cmd;
+       struct mmc_data *data;
        u32 irq_en, status, raw_status;
        irqreturn_t ret = IRQ_HANDLED;
 
@@ -590,14 +727,11 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
 
        cmd = host->cmd;
 
-       mrq = host->mrq;
-
-       if (WARN_ON(!mrq))
-               return IRQ_NONE;
-
        if (WARN_ON(!cmd))
                return IRQ_NONE;
 
+       data = cmd->data;
+
        spin_lock(&host->lock);
        irq_en = readl(host->regs + SD_EMMC_IRQ_EN);
        raw_status = readl(host->regs + SD_EMMC_STATUS);
@@ -610,6 +744,8 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
                goto out;
        }
 
+       meson_mmc_read_resp(host->mmc, cmd);
+
        cmd->error = 0;
        if (status & IRQ_RXD_ERR_MASK) {
                dev_dbg(host->dev, "Unhandled IRQ: RXD error\n");
@@ -636,12 +772,16 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
        if (status & IRQ_SDIO)
                dev_dbg(host->dev, "Unhandled IRQ: SDIO.\n");
 
-       if (status & (IRQ_END_OF_CHAIN | IRQ_RESP_STATUS))
-               ret = IRQ_WAKE_THREAD;
-       else  {
+       if (status & (IRQ_END_OF_CHAIN | IRQ_RESP_STATUS)) {
+               if (data && !cmd->error)
+                       data->bytes_xfered = data->blksz * data->blocks;
+               if (meson_mmc_bounce_buf_read(data) ||
+                   meson_mmc_get_next_command(cmd))
+                       ret = IRQ_WAKE_THREAD;
+       } else {
                dev_warn(host->dev, "Unknown IRQ! status=0x%04x: MMC CMD%u arg=0x%08x flags=0x%08x stop=%d\n",
                         status, cmd->opcode, cmd->arg,
-                        cmd->flags, mrq->stop ? 1 : 0);
+                        cmd->flags, cmd->mrq->stop ? 1 : 0);
                if (cmd->data) {
                        struct mmc_data *data = cmd->data;
 
@@ -656,10 +796,8 @@ out:
        /* ack all (enabled) interrupts */
        writel(status, host->regs + SD_EMMC_STATUS);
 
-       if (ret == IRQ_HANDLED) {
-               meson_mmc_read_resp(host->mmc, cmd);
+       if (ret == IRQ_HANDLED)
                meson_mmc_request_done(host->mmc, cmd->mrq);
-       }
 
        spin_unlock(&host->lock);
        return ret;
@@ -668,35 +806,53 @@ out:
 static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id)
 {
        struct meson_host *host = dev_id;
-       struct mmc_request *mrq = host->mrq;
-       struct mmc_command *cmd = host->cmd;
+       struct mmc_command *next_cmd, *cmd = host->cmd;
        struct mmc_data *data;
        unsigned int xfer_bytes;
 
-       if (WARN_ON(!mrq))
-               return IRQ_NONE;
-
        if (WARN_ON(!cmd))
                return IRQ_NONE;
 
        data = cmd->data;
-       if (data && data->flags & MMC_DATA_READ) {
+       if (meson_mmc_bounce_buf_read(data)) {
                xfer_bytes = data->blksz * data->blocks;
                WARN_ON(xfer_bytes > host->bounce_buf_size);
                sg_copy_from_buffer(data->sg, data->sg_len,
                                    host->bounce_buf, xfer_bytes);
-               data->bytes_xfered = xfer_bytes;
        }
 
-       meson_mmc_read_resp(host->mmc, cmd);
-       if (!data || !data->stop || mrq->sbc)
-               meson_mmc_request_done(host->mmc, mrq);
+       next_cmd = meson_mmc_get_next_command(cmd);
+       if (next_cmd)
+               meson_mmc_start_cmd(host->mmc, next_cmd);
        else
-               meson_mmc_start_cmd(host->mmc, data->stop);
+               meson_mmc_request_done(host->mmc, cmd->mrq);
 
        return IRQ_HANDLED;
 }
 
+static int meson_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+       struct meson_host *host = mmc_priv(mmc);
+       struct meson_tuning_params tp_old = host->tp;
+       int ret = -EINVAL, i, cmd_error;
+
+       dev_info(mmc_dev(mmc), "(re)tuning...\n");
+
+       for (i = CLK_PHASE_0; i <= CLK_PHASE_270; i++) {
+               host->tp.rx_phase = i;
+               /* exclude the active parameter set if retuning */
+               if (!memcmp(&tp_old, &host->tp, sizeof(tp_old)) &&
+                   mmc->doing_retune)
+                       continue;
+               meson_mmc_set_tuning_params(mmc);
+               ret = mmc_send_tuning(mmc, opcode, &cmd_error);
+               if (!ret)
+                       break;
+       }
+
+       return ret;
+}
+
 /*
  * NOTE: we only need this until the GPIO/pinctrl driver can handle
  * interrupts.  For now, the MMC core will use this for polling.
@@ -711,10 +867,25 @@ static int meson_mmc_get_cd(struct mmc_host *mmc)
        return status;
 }
 
+static void meson_mmc_cfg_init(struct meson_host *host)
+{
+       u32 cfg = 0;
+
+       cfg |= FIELD_PREP(CFG_RESP_TIMEOUT_MASK,
+                         ilog2(SD_EMMC_CFG_RESP_TIMEOUT));
+       cfg |= FIELD_PREP(CFG_RC_CC_MASK, ilog2(SD_EMMC_CFG_CMD_GAP));
+       cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, ilog2(SD_EMMC_CFG_BLK_SIZE));
+
+       writel(cfg, host->regs + SD_EMMC_CFG);
+}
+
 static const struct mmc_host_ops meson_mmc_ops = {
        .request        = meson_mmc_request,
        .set_ios        = meson_mmc_set_ios,
        .get_cd         = meson_mmc_get_cd,
+       .pre_req        = meson_mmc_pre_req,
+       .post_req       = meson_mmc_post_req,
+       .execute_tuning = meson_mmc_execute_tuning,
 };
 
 static int meson_mmc_probe(struct platform_device *pdev)
@@ -722,7 +893,7 @@ static int meson_mmc_probe(struct platform_device *pdev)
        struct resource *res;
        struct meson_host *host;
        struct mmc_host *mmc;
-       int ret;
+       int ret, irq;
 
        mmc = mmc_alloc_host(sizeof(struct meson_host), &pdev->dev);
        if (!mmc)
@@ -754,8 +925,8 @@ static int meson_mmc_probe(struct platform_device *pdev)
                goto free_host;
        }
 
-       host->irq = platform_get_irq(pdev, 0);
-       if (host->irq == 0) {
+       irq = platform_get_irq(pdev, 0);
+       if (!irq) {
                dev_err(&pdev->dev, "failed to get interrupt resource.\n");
                ret = -EINVAL;
                goto free_host;
@@ -771,9 +942,13 @@ static int meson_mmc_probe(struct platform_device *pdev)
        if (ret)
                goto free_host;
 
+       host->tp.core_phase = CLK_PHASE_180;
+       host->tp.tx_phase = CLK_PHASE_0;
+       host->tp.rx_phase = CLK_PHASE_0;
+
        ret = meson_mmc_clk_init(host);
        if (ret)
-               goto free_host;
+               goto err_core_clk;
 
        /* Stop execution */
        writel(0, host->regs + SD_EMMC_START);
@@ -783,14 +958,20 @@ static int meson_mmc_probe(struct platform_device *pdev)
        writel(IRQ_EN_MASK, host->regs + SD_EMMC_STATUS);
        writel(IRQ_EN_MASK, host->regs + SD_EMMC_IRQ_EN);
 
-       ret = devm_request_threaded_irq(&pdev->dev, host->irq,
-                                       meson_mmc_irq, meson_mmc_irq_thread,
-                                       IRQF_SHARED, DRIVER_NAME, host);
+       /* set config to sane default */
+       meson_mmc_cfg_init(host);
+
+       ret = devm_request_threaded_irq(&pdev->dev, irq, meson_mmc_irq,
+                                       meson_mmc_irq_thread, IRQF_SHARED,
+                                       NULL, host);
        if (ret)
-               goto free_host;
+               goto err_div_clk;
 
+       mmc->caps |= MMC_CAP_CMD23;
        mmc->max_blk_count = CMD_CFG_LENGTH_MASK;
        mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size;
+       mmc->max_segs = SD_EMMC_DESC_BUF_LEN / sizeof(struct sd_emmc_desc);
+       mmc->max_seg_size = mmc->max_req_size;
 
        /* data bounce buffer */
        host->bounce_buf_size = mmc->max_req_size;
@@ -800,7 +981,15 @@ static int meson_mmc_probe(struct platform_device *pdev)
        if (host->bounce_buf == NULL) {
                dev_err(host->dev, "Unable to map allocate DMA bounce buffer.\n");
                ret = -ENOMEM;
-               goto free_host;
+               goto err_div_clk;
+       }
+
+       host->descs = dma_alloc_coherent(host->dev, SD_EMMC_DESC_BUF_LEN,
+                     &host->descs_dma_addr, GFP_KERNEL);
+       if (!host->descs) {
+               dev_err(host->dev, "Allocating descriptor DMA buffer failed\n");
+               ret = -ENOMEM;
+               goto err_bounce_buf;
        }
 
        mmc->ops = &meson_mmc_ops;
@@ -808,9 +997,14 @@ static int meson_mmc_probe(struct platform_device *pdev)
 
        return 0;
 
-free_host:
+err_bounce_buf:
+       dma_free_coherent(host->dev, host->bounce_buf_size,
+                         host->bounce_buf, host->bounce_dma_addr);
+err_div_clk:
        clk_disable_unprepare(host->cfg_div_clk);
+err_core_clk:
        clk_disable_unprepare(host->core_clk);
+free_host:
        mmc_free_host(mmc);
        return ret;
 }
@@ -819,9 +1013,13 @@ static int meson_mmc_remove(struct platform_device *pdev)
 {
        struct meson_host *host = dev_get_drvdata(&pdev->dev);
 
+       mmc_remove_host(host->mmc);
+
        /* disable interrupts */
        writel(0, host->regs + SD_EMMC_IRQ_EN);
 
+       dma_free_coherent(host->dev, SD_EMMC_DESC_BUF_LEN,
+                         host->descs, host->descs_dma_addr);
        dma_free_coherent(host->dev, host->bounce_buf_size,
                          host->bounce_buf, host->bounce_dma_addr);