From a08358dce9044c8c184e81b2be5be270e3a89c7e Mon Sep 17 00:00:00 2001
From: Martin Stein <martin.stein@genode-labs.com>
Date: Thu, 22 Oct 2015 17:06:00 +0200
Subject: [PATCH] sd_card & imx53: fix multiblock write termination

The manual termination of multi-block writes via "Stop Transmission" commands
seems to leave the card in a busy state sometimes. This causes errors on
subsequent commands. Thus, we have to synchronize manually with the
card-internal state via "Send State" commands. Additionally, the method
for issuing the manual "Stop Transmission" commands was refined.

Ref #1497
---
 repos/os/src/drivers/sd_card/sd_card.h        | 36 +++++++++++
 .../src/drivers/sd_card/spec/imx53/esdhcv2.h  | 61 ++++++++++++++++++-
 2 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/repos/os/src/drivers/sd_card/sd_card.h b/repos/os/src/drivers/sd_card/sd_card.h
index d18e62a024..25e2539a61 100644
--- a/repos/os/src/drivers/sd_card/sd_card.h
+++ b/repos/os/src/drivers/sd_card/sd_card.h
@@ -22,6 +22,34 @@ namespace Sd_card {
 
 	using namespace Genode;
 
+	/**
+	 * Structure of the first word of a native mode R1 response
+	 */
+	struct R1_response_0 : Register<32>
+	{
+		struct Ready_for_data : Bitfield<8, 1> { };
+		struct State          : Bitfield<9, 4>
+		{
+			enum { PROGRAM = 7 };
+		};
+		struct Error          : Bitfield<19, 1> { };
+
+		/**
+		 * Return wether the card is ready for data
+		 *
+		 * \param resp  R1 response, word 0
+		 */
+		static bool card_ready(access_t const resp)
+		{
+			/*
+			 * Check both ready bit and state because not all cards handle
+			 * the status bits correctly.
+			 */
+			return Ready_for_data::get(resp) &&
+			       State::get(resp) != State::PROGRAM;
+		}
+	};
+
 	/**
 	 * Returned by 'Sd_send_op_cond'
 	 */
@@ -149,6 +177,14 @@ namespace Sd_card {
 		}
 	};
 
+	struct Send_status : Command<13, RESPONSE_48_BIT>
+	{
+		struct Arg : Sd_card::Arg
+		{
+			struct Rca : Bitfield<16, 16> { };
+		};
+	};
+
 	struct Select_card : Command<7, RESPONSE_48_BIT>
 	{
 		struct Arg : Sd_card::Arg
diff --git a/repos/os/src/drivers/sd_card/spec/imx53/esdhcv2.h b/repos/os/src/drivers/sd_card/spec/imx53/esdhcv2.h
index a509595496..dec19f2f94 100644
--- a/repos/os/src/drivers/sd_card/spec/imx53/esdhcv2.h
+++ b/repos/os/src/drivers/sd_card/spec/imx53/esdhcv2.h
@@ -177,6 +177,8 @@ struct Esdhcv2 : Genode::Mmio
 				_48BIT_BUSY = 3,
 			};
 		};
+		struct Cccen  : Bitfield<19, 1> { };
+		struct Cicen  : Bitfield<20, 1> { };
 		struct Dpsel  : Bitfield<21, 1> { };
 		struct Cmdtyp : Bitfield<22, 2>
 		{
@@ -626,6 +628,57 @@ struct Esdhcv2_controller : private Esdhcv2, public Sd_card::Host_controller
 			return true;
 		}
 
+		int _wait_for_card_ready_mbw()
+		{
+			/*
+			 * Poll card status
+			 *
+			 * The maximum number of attempts and the delay between two attempts are
+			 * freely chosen.
+			 */
+			using namespace Sd_card;
+			unsigned           attempts          = 5;
+			unsigned constexpr attempts_delay_us = 100000;
+			while (1) {
+				if (!attempts) {
+					PERR("Reading card status after multiblock write failed");
+					return -1;
+				}
+				/* assemble argument register value */
+				Send_status::Arg::access_t cmdarg = 0;
+				Send_status::Arg::Rca::set(cmdarg, _card_info.rca());
+
+				/* assemble command register value */
+				Xfertyp::access_t xfertyp = 0;
+				Xfertyp::Cmdinx::set(xfertyp, Send_status::INDEX);
+				Xfertyp::Cicen::set(xfertyp, 1);
+				Xfertyp::Cccen::set(xfertyp, 1);
+				Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT);
+				Xfertyp::Msbsel::set(xfertyp,  1);
+				Xfertyp::Bcen::set(xfertyp, 1);
+				Xfertyp::Dmaen::set(xfertyp, 1);
+
+				/* send command as soon as the host allows it */
+				if (_wait_for_cmd_allowed()) { return -1; }
+				write<Cmdarg>(cmdarg);
+				write<Xfertyp>(xfertyp);
+
+				/* wait for command completion */
+				if (_wait_for_cmd_complete()) { return -1; }
+
+				/* check for errors */
+				R1_response_0::access_t const resp = read<Cmdrsp0>();
+				if (R1_response_0::Error::get(resp)) {
+					PERR("Reading card status after multiblock write failed");
+					return -1;
+				}
+				/* if card is in a ready state, return success, retry otherwise */
+				if (R1_response_0::card_ready(resp)) { break; }
+				_delayer.usleep(attempts_delay_us);
+			}
+			return 0;
+		}
+
 		/**
 		 * Abort transmission by manually issuing stop command
 		 *
@@ -637,7 +690,12 @@ struct Esdhcv2_controller : private Esdhcv2, public Sd_card::Host_controller
 			Xfertyp::access_t xfertyp = 0;
 			Xfertyp::Cmdinx::set(xfertyp, Sd_card::Stop_transmission::INDEX);
 			Xfertyp::Cmdtyp::set(xfertyp, Xfertyp::Cmdtyp::ABORT_CMD12);
-			Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT);
+			Xfertyp::Cccen::set(xfertyp, 1);
+			Xfertyp::Cicen::set(xfertyp, 1);
+			Xfertyp::Rsptyp::set(xfertyp, Xfertyp::Rsptyp::_48BIT_BUSY);
+			Xfertyp::Msbsel::set(xfertyp, 1);
+			Xfertyp::Bcen::set(xfertyp, 1);
+			Xfertyp::Dmaen::set(xfertyp, 1);
 			write<Xfertyp>(xfertyp);
 			return _wait_for_cmd_complete();
 		}
@@ -686,6 +744,7 @@ struct Esdhcv2_controller : private Esdhcv2, public Sd_card::Host_controller
 				 * manually.
 				 */
 				if (!_abort_transmission())  { return false; }
+				if (_wait_for_card_ready_mbw()) { return false; }
 			}
 			return true;
 		}