mmc: core: fix shutdown in cmdq mode

During the shutdown process,
	- Stop the queue
	- Flush all the pending requests
	- Halt the Controller
	- Put the card in legacy mode

Change-Id: I5fe4e998bc04bfd8f50de63f3beae3cb25f1c8ba
Signed-off-by: Asutosh Das <asutoshd@codeaurora.org>
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
[xiaonian@codeaurora.org: fixed trivial merge conflicts]
Signed-off-by: Xiaonian Wang <xiaonian@codeaurora.org>
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index 8d44e4a..d08676c 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -3026,6 +3026,34 @@
 	clear_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state);
 }
 
+static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq)
+{
+	int err;
+	struct mmc_card *card = mq->card;
+	struct mmc_host *host = card->host;
+
+	err = mmc_cmdq_halt(host, true);
+	if (err) {
+		pr_err("%s: halt: failed: %d\n", __func__, err);
+		return;
+	}
+
+	mmc_claim_host(card->host);
+	/* disable CQ mode in card */
+	err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+			 EXT_CSD_CMDQ, 0,
+			 card->ext_csd.generic_cmd6_time);
+	if (err) {
+		pr_err("%s: failed to switch card to legacy mode: %d\n",
+		       __func__, err);
+		goto out;
+	} else {
+		host->card->cmdq_init = false;
+	}
+out:
+	mmc_release_host(card->host);
+}
+
 static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req)
 {
 	struct mmc_queue *mq = req->q->queuedata;
@@ -3171,6 +3199,9 @@
 			test_and_clear_bit(0, &ctx_info->req_starved))
 		blk_run_queue(mq->queue);
 	mmc_release_host(host);
+
+	if (blk_queue_stopped(mq->queue) && !ctx_info->active_reqs)
+		complete(&mq->cmdq_shutdown_complete);
 	return;
 }
 
@@ -3640,6 +3671,7 @@
 		md->queue.cmdq_issue_fn = mmc_blk_cmdq_issue_rq;
 		md->queue.cmdq_error_fn = mmc_blk_cmdq_err;
 		md->queue.cmdq_req_timed_out = mmc_blk_cmdq_req_timed_out;
+		md->queue.cmdq_shutdown = mmc_blk_cmdq_shutdown;
 	}
 
 	if (mmc_card_mmc(card) && !card->cmdq_init &&
diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c
index 2990a7b..14553c2 100644
--- a/drivers/mmc/card/queue.c
+++ b/drivers/mmc/card/queue.c
@@ -632,6 +632,7 @@
 
 	blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done);
 	INIT_WORK(&mq->cmdq_err_work, mmc_cmdq_error_work);
+	init_completion(&mq->cmdq_shutdown_complete);
 
 	blk_queue_rq_timed_out(mq->queue, mmc_cmdq_rq_timed_out);
 	blk_queue_rq_timeout(mq->queue, 120 * HZ);
@@ -663,6 +664,12 @@
 	mq->mqrq_cmdq = NULL;
 }
 
+static void mmc_wait_for_pending_reqs(struct mmc_queue *mq)
+{
+	wait_for_completion(&mq->cmdq_shutdown_complete);
+	mq->cmdq_shutdown(mq);
+}
+
 /**
  * mmc_queue_suspend - suspend a MMC request queue
  * @mq: MMC queue to suspend
@@ -677,6 +684,27 @@
 	struct request_queue *q = mq->queue;
 	unsigned long flags;
 	int rc = 0;
+	struct mmc_card *card = mq->card;
+
+	if (card->cmdq_init) {
+		struct mmc_host *host = card->host;
+		unsigned long flags;
+
+		spin_lock_irqsave(q->queue_lock, flags);
+		blk_stop_queue(q);
+		spin_unlock_irqrestore(q->queue_lock, flags);
+
+		if (host->cmdq_ctx.active_reqs) {
+			if (!wait)
+				rc = -EBUSY;
+			else
+				mmc_wait_for_pending_reqs(mq);
+		} else {
+			mq->cmdq_shutdown(mq);
+		}
+
+		goto out;
+	}
 
 	if (!(test_and_set_bit(MMC_QUEUE_SUSPENDED, &mq->flags))) {
 		spin_lock_irqsave(q->queue_lock, flags);
@@ -699,6 +727,7 @@
 			rc = 0;
 		}
 	}
+out:
 	return rc;
 }
 
diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h
index da6a97f..12b08b0 100644
--- a/drivers/mmc/card/queue.h
+++ b/drivers/mmc/card/queue.h
@@ -75,9 +75,11 @@
 	int			num_wr_reqs_to_start_packing;
 	bool			no_pack_for_random;
 	struct work_struct	cmdq_err_work;
+	struct completion	cmdq_shutdown_complete;
 
 	int (*err_check_fn)(struct mmc_card *, struct mmc_async_req *);
 	void (*packed_test_fn)(struct request_queue *, struct mmc_queue_req *);
+	void (*cmdq_shutdown)(struct mmc_queue *);
 #ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
 	atomic_t max_write_speed;
 	atomic_t max_read_speed;