/*
 * \brief  SD-card protocol
 * \author Christian Helmuth
 * \date   2011-05-19
 */

/*
 * Copyright (C) 2011-2012 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _SD_CARD_H_
#define _SD_CARD_H_

#include <block/driver.h>

#include "host_driver.h"


class Sd_card : public Block::Driver
{
	private:

		Host_driver &_hd;

		enum { BLOCK_SIZE = 512 };

	public:

		Sd_card(Host_driver &host_driver) : _hd(host_driver)
		{
			unsigned resp;

			/* CMD0: go idle state */
			_hd.request(0, 0);

			/*
			 * CMD8: send interface condition
			 *
			 * XXX only one hard-coded value currently.
			 */
			_hd.request(8, 0x1aa, &resp);

			/*
			 * ACMD41: card send operating condition
			 *
			 * This is an application-specific command and, therefore, consists
			 * of prefix command CMD55 + CMD41.
			 */
			_hd.request(55, 0, &resp);
			_hd.request(41, 0x4000, &resp);

			/* CMD2: all send card identification (CID) */
			_hd.request(2, &resp);

			/* CMD3: send relative card address (RCA) */
			_hd.request(3, &resp);
			unsigned short rca = resp >> 16;

			/*
			 * Now, the card is in transfer mode...
			 */

			/* CMD7: select card */
			_hd.request(7, rca << 16, &resp);
		}

		Host_driver &host_driver() { return _hd; }

		/****************************
		 ** Block-driver interface **
		 ****************************/

		Genode::size_t block_size()  { return BLOCK_SIZE; }
		/*
		 * TODO report (and support) real capacity not just 512M
		 */
		Genode::size_t block_count() { return 0x20000000 /  BLOCK_SIZE; }

		void read(Genode::size_t  block_number,
		          Genode::size_t  block_count,
		          char           *out_buffer)
		{
			unsigned resp;
			unsigned length = BLOCK_SIZE;

			for (Genode::size_t i = 0; i < block_count; ++i) {
				/*
				 * CMD17: read single block
				 *
				 * SDSC cards use a byte address as argument while SDHC/SDSC uses a
				 * block address here.
				 */
				_hd.read_request(17, (block_number + i) * BLOCK_SIZE,
				                 length, &resp);
				_hd.read_data(length, out_buffer + (i * BLOCK_SIZE));
			}
		}

		void write(Genode::size_t  block_number,
		           Genode::size_t  block_count,
		           char const     *buffer)
		{
			unsigned resp;
			unsigned length = BLOCK_SIZE;

			for (Genode::size_t i = 0; i < block_count; ++i) {
				/*
				 * CMD24: write single block
				 *
				 * SDSC cards use a byte address as argument while SDHC/SDSC uses a
				 * block address here.
				 */
				_hd.write_request(24, (block_number + i) * BLOCK_SIZE,
				                  length, &resp);
				_hd.write_data(length, buffer + (i * BLOCK_SIZE));
			}
		}

		/*
		 * This driver does not support DMA operation, currently.
		 */

		void read_dma(Genode::size_t, Genode::size_t, Genode::addr_t) {
			throw Io_error(); }

		void write_dma(Genode::size_t, Genode::size_t, Genode::addr_t) {
			throw Io_error(); }

		bool dma_enabled() { return false; }
};

#endif /* _SD_CARD_H_ */
