CMD23-prefixed instead of open-ended multiblock transfers
have a performance advantage on some MMC cards.

Signed-off-by: Andrei Warkentin <andr...@motorola.com>
---
 drivers/mmc/card/block.c |   89 ++++++++++++++++++++++++++++++---------------
 include/linux/mmc/core.h |    1 +
 include/linux/mmc/host.h |    6 +++
 include/linux/mmc/mmc.h  |    6 +++
 4 files changed, 72 insertions(+), 30 deletions(-)

diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index c209953..282e6ef 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -55,10 +55,6 @@ MODULE_ALIAS("mmc:block");
 #define INAND_CMD38_ARG_SECTRIM1 0x81
 #define INAND_CMD38_ARG_SECTRIM2 0x88
 
-#define REL_WRITES_SUPPORTED(card) (mmc_card_mmc((card)) &&    \
-    (((card)->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN) ||  \
-     ((card)->ext_csd.rel_sectors)))
-
 static DEFINE_MUTEX(block_mutex);
 
 /*
@@ -85,6 +81,10 @@ struct mmc_blk_data {
        struct mmc_queue queue;
        struct list_head part;
 
+       unsigned int    flags;
+#define MMC_BLK_CMD23  (1 << 0)        /* Can do SET_BLOCK_COUNT for 
multiblock */
+#define MMC_BLK_REL_WR (1 << 1)        /* MMC Reliable write support */
+
        unsigned int    usage;
        unsigned int    read_only;
        unsigned int    part_type;
@@ -225,6 +225,7 @@ static const struct block_device_operations mmc_bdops = {
 
 struct mmc_blk_request {
        struct mmc_request      mrq;
+       struct mmc_command      sbc;
        struct mmc_command      cmd;
        struct mmc_command      stop;
        struct mmc_data         data;
@@ -455,13 +456,10 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, 
struct request *req)
  * reliable write can handle, thus finish the request in
  * partial completions.
  */
-static inline int mmc_apply_rel_rw(struct mmc_blk_request *brq,
-                                  struct mmc_card *card,
-                                  struct request *req)
+static inline void mmc_apply_rel_rw(struct mmc_blk_request *brq,
+                                   struct mmc_card *card,
+                                   struct request *req)
 {
-       int err;
-       struct mmc_command set_count;
-
        if (!(card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN)) {
                /* Legacy mode imposes restrictions on transfers. */
                if (!IS_ALIGNED(brq->cmd.arg, card->ext_csd.rel_sectors))
@@ -472,16 +470,6 @@ static inline int mmc_apply_rel_rw(struct mmc_blk_request 
*brq,
                else if (brq->data.blocks < card->ext_csd.rel_sectors)
                        brq->data.blocks = 1;
        }
-
-       memset(&set_count, 0, sizeof(struct mmc_command));
-       set_count.opcode = MMC_SET_BLOCK_COUNT;
-       set_count.arg = brq->data.blocks | (1 << 31);
-       set_count.flags = MMC_RSP_R1 | MMC_CMD_AC;
-       err = mmc_wait_for_cmd(card->host, &set_count, 0);
-       if (err)
-               printk(KERN_ERR "%s: error %d SET_BLOCK_COUNT\n",
-                      req->rq_disk->disk_name, err);
-       return err;
 }
 
 static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *req)
@@ -498,7 +486,7 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct 
request *req)
        bool do_rel_wr = ((req->cmd_flags & REQ_FUA) ||
                          (req->cmd_flags & REQ_META)) &&
                (rq_data_dir(req) == WRITE) &&
-               REL_WRITES_SUPPORTED(card);
+               (md->flags & MMC_BLK_REL_WR);
 
        do {
                struct mmc_command cmd;
@@ -536,11 +524,9 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, 
struct request *req)
 
                if (brq.data.blocks > 1 || do_rel_wr) {
                        /* SPI multiblock writes terminate using a special
-                        * token, not a STOP_TRANSMISSION request. Reliable
-                        * writes use SET_BLOCK_COUNT and do not use a
-                        * STOP_TRANSMISSION request either.
+                        * token, not a STOP_TRANSMISSION request.
                         */
-                       if ((!mmc_host_is_spi(card->host) && !do_rel_wr) ||
+                       if (!mmc_host_is_spi(card->host) ||
                            rq_data_dir(req) == READ)
                                brq.mrq.stop = &brq.stop;
                        readcmd = MMC_READ_MULTIPLE_BLOCK;
@@ -558,8 +544,32 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, 
struct request *req)
                        brq.data.flags |= MMC_DATA_WRITE;
                }
 
-               if (do_rel_wr && mmc_apply_rel_rw(&brq, card, req))
-                       goto cmd_err;
+               if (do_rel_wr)
+                       mmc_apply_rel_rw(&brq, card, req);
+
+               /*
+                * Pre-defined multi-block transfers are preferable to
+                * open ended-ones (and necessary for reliable writes).
+                * However, it is not sufficient to just send CMD23,
+                * and avoid the final CMD12, as on an error condition
+                * CMD12 (stop) needs to be sent anyway. This, coupled
+                * with Auto-CMD23 enhancements provided by some
+                * hosts, means that the complexity of dealing
+                * with this is best left to the host. If CMD23 is
+                * supported by card and host, we'll fill sbc in and let
+                * the host deal with handling it correctly. This means
+                * that for hosts that don't expose MMC_CAP_CMD23, no
+                * change of behavior will be observed.
+                */
+
+               if ((md->flags & MMC_BLK_CMD23) &&
+                   mmc_op_multi(brq.cmd.opcode)) {
+                       brq.sbc.opcode = MMC_SET_BLOCK_COUNT;
+                       brq.sbc.arg = brq.data.blocks |
+                               (do_rel_wr ? (1 << 31) : 0);
+                       brq.sbc.flags = MMC_RSP_R1 | MMC_CMD_AC;
+                       brq.mrq.sbc = &brq.sbc;
+               }
 
                mmc_set_data_timeout(&brq.data, card);
 
@@ -596,7 +606,8 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct 
request *req)
                 * until later as we need to wait for the card to leave
                 * programming mode even when things go wrong.
                 */
-               if (brq.cmd.error || brq.data.error || brq.stop.error) {
+               if (brq.sbc.error || brq.cmd.error ||
+                   brq.data.error || brq.stop.error) {
                        if (brq.data.blocks > 1 && rq_data_dir(req) == READ) {
                                /* Redo read one sector at a time */
                                printk(KERN_WARNING "%s: retrying using single "
@@ -607,6 +618,13 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, 
struct request *req)
                        status = get_card_status(card, req);
                }
 
+               if (brq.sbc.error) {
+                       printk(KERN_ERR "%s: error %d sending SET_BLOCK_COUNT "
+                              "command, response %#x, card status %#x\n",
+                              req->rq_disk->disk_name, brq.sbc.error,
+                              brq.sbc.resp[0], status);
+               }
+
                if (brq.cmd.error) {
                        printk(KERN_ERR "%s: error %d sending read/write "
                               "command, response %#x, card status %#x\n",
@@ -804,8 +822,6 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct 
mmc_card *card,
        md->disk->queue = md->queue.queue;
        md->disk->driverfs_dev = parent;
        set_disk_ro(md->disk, md->read_only || default_ro);
-       if (REL_WRITES_SUPPORTED(card))
-               blk_queue_flush(md->queue.queue, REQ_FLUSH | REQ_FUA);
 
        /*
         * As discussed on lkml, GENHD_FL_REMOVABLE should:
@@ -829,6 +845,19 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct 
mmc_card *card,
 
        blk_queue_logical_block_size(md->queue.queue, 512);
        set_capacity(md->disk, size);
+
+       if (mmc_host_cmd23(card->host) &&
+           mmc_card_mmc(card))
+               md->flags |= MMC_BLK_CMD23;
+
+       if (mmc_card_mmc(card) &&
+           md->flags & MMC_BLK_CMD23 &&
+           ((card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN) ||
+            card->ext_csd.rel_sectors)) {
+               md->flags |= MMC_BLK_REL_WR;
+               blk_queue_flush(md->queue.queue, REQ_FLUSH | REQ_FUA);
+       }
+
        return md;
 
  err_putdisk:
diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h
index f8e4bcb..55d7fde 100644
--- a/include/linux/mmc/core.h
+++ b/include/linux/mmc/core.h
@@ -120,6 +120,7 @@ struct mmc_data {
 };
 
 struct mmc_request {
+       struct mmc_command      *sbc;           /* SET_BLOCK_COUNT for 
multiblock */
        struct mmc_command      *cmd;
        struct mmc_data         *data;
        struct mmc_command      *stop;
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 4f705eb..ba34fc5 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -173,6 +173,7 @@ struct mmc_host {
                                                /* DDR mode at 1.2V */
 #define MMC_CAP_POWER_OFF_CARD (1 << 13)       /* Can power off after boot */
 #define MMC_CAP_BUS_WIDTH_TEST (1 << 14)       /* CMD14/CMD19 bus width ok */
+#define MMC_CAP_CMD23          (1 << 15)       /* CMD23 supported */
 
        mmc_pm_flag_t           pm_caps;        /* supported pm features */
 
@@ -330,5 +331,10 @@ static inline int mmc_card_wake_sdio_irq(struct mmc_host 
*host)
 {
        return host->pm_flags & MMC_PM_WAKE_SDIO_IRQ;
 }
+
+static inline int mmc_host_cmd23(struct mmc_host *host)
+{
+       return host->caps & MMC_CAP_CMD23;
+}
 #endif
 
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index 373b2bf..ea74168 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -82,6 +82,12 @@
 #define MMC_APP_CMD              55   /* ac   [31:16] RCA        R1  */
 #define MMC_GEN_CMD              56   /* adtc [0] RD/WR          R1  */
 
+static inline bool mmc_op_multi(u32 opcode)
+{
+       return opcode == MMC_WRITE_MULTIPLE_BLOCK ||
+               opcode == MMC_READ_MULTIPLE_BLOCK;
+}
+
 /*
  * MMC_SWITCH argument format:
  *
-- 
1.7.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to