/*
 * \brief  Exynos5-specific implementation of the Block::Driver interface
 * \author Sebastian Sumpf
 * \author Martin Stein
 * \date   2013-03-22
 */

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

/* local includes */
#include <driver.h>

using namespace Genode;
using namespace Sd_card;


Driver::Driver(Env &env)
:
	Driver_base(env.ram()),
	Attached_mmio(env, MSH_BASE, MSH_SIZE), _env(env)
{
	_irq.sigh(_irq_handler);
	_irq.ack_irq();

	log("SD/MMC card detected");
	log("capacity: ", _card_info.capacity_mb(), " MiB");
}


void Driver::read_dma(Block::sector_t           block_number,
                      size_t                    block_count,
                      addr_t                    buf_phys,
                      Block::Packet_descriptor &pkt)
{
	if (_block_transfer.pending) {
		throw Request_congestion(); }

	if (!_setup_idmac_descriptor_table(block_count, buf_phys))
		throw Io_error();

	_block_transfer.packet  = pkt;
	_block_transfer.pending = true;

	if (!_issue_command(Read_multiple_block(block_number))) {
		error("Read_multiple_block failed");
		throw Io_error();
	}
}


void Driver::write_dma(Block::sector_t           block_number,
                       size_t                    block_count,
                       addr_t                    buf_phys,
                       Block::Packet_descriptor &pkt)
{
	if (_block_transfer.pending) {
		throw Request_congestion(); }

	if (!_setup_idmac_descriptor_table(block_count, buf_phys))
		throw Io_error();

	_block_transfer.packet  = pkt;
	_block_transfer.pending = true;

	if (!_issue_command(Write_multiple_block(block_number))) {
		error("Read_multiple_block failed");
		throw Io_error();
	}
}


bool Driver::_reset()
{
	Mmio::write<Ctrl::Reset>(0x7);
	try { wait_for(Attempts(100), Microseconds(1000), _delayer,
	               Ctrl::Reset::Equal(0)); }
	catch (Polling_timeout) {
		error("Could not reset host contoller");
		return false;
	}
	return true;
}


void Driver::_reset_fifo()
{
	Mmio::write<Ctrl::Reset>(0x2);
	try { wait_for(Attempts(100), Microseconds(1000), _delayer,
	               Ctrl::Reset::Equal(0)); }
	catch (Polling_timeout) {
		error("Could not reset fifo"); }
}


void Driver::_disable_irq()
{
	Mmio::write<Rintsts>(~0U);
	Mmio::write<Intmask>(0);
}


bool Driver::_update_clock_registers()
{
	Cmd::access_t cmd = 0;
	Cmd::Wait_prvdata_complete::set(cmd, 1);
	Cmd::Update_clock_registers_only::set(cmd, 1);
	Cmd::Start_cmd::set(cmd, 1);
	Mmio::write<Cmd>(cmd);

	try { wait_for(_delayer, Cmd::Start_cmd::Equal(0)); }
	catch (Polling_timeout) {
		error("Update clock registers failed");
		return false;
	}
	return true;
}


bool Driver::_setup_bus(unsigned clock_div)
{
	/* set host clock divider */
	Mmio::write<Clkdiv>(clock_div);

	if (!_update_clock_registers())
		return false;

	/* enable clock for card 1 */
	Mmio::write<Clkena>(0x1);

	if (!_update_clock_registers())
		return false;

	_delayer.usleep(10 * 1000);
	return true;
}


Card_info Driver::_init()
{
	Mmio::write<Pwren>(1);
	if (!_reset())
		throw Detection_failed();

	Mmio::write<Emmc_ddr_req>(0x1);

	_disable_irq();

	Mmio::write<Tmout>(~0U);
	Mmio::write<Idinten>(0);
	Mmio::write<Bmod>(1);
	Mmio::write<Bytcnt>(0);
	Mmio::write<Fifoth>(0x203f0040);

	/* set to one bit transfer Bit */
	if (!_setup_bus(CLK_DIV_400Khz))
		throw Detection_failed();

	Mmio::write<Ctype>(BUS_WIDTH_1);

	if (!issue_command(Go_idle_state())) {
		warning("Go_idle_state command failed");
		throw Detection_failed();
	}
	_delayer.usleep(2000);

	if (!issue_command(Send_if_cond())) {
		warning("Send_if_cond command failed");
		throw Detection_failed();
	}
	 /* if this succeeds it is an SD card */
	if ((Mmio::read<Rsp0>() & 0xff) == 0xaa)
		log("Found SD card");

	/*
	 * We need to issue the same Mmc_send_op_cond command multiple
	 * times. The first time, we receive the status information. On
	 * subsequent attempts, the response tells us that the card is
	 * busy. Usually, the command is issued twice. We give up if the
	 * card is not reaching busy state after one second.
	 */
	unsigned i = 1000;
	unsigned voltages = 0x300080;
	unsigned arg = 0;
	for (; i > 0; --i) {
		if (!issue_command(Mmc_send_op_cond(arg, true))) {
			warning("Sd_send_op_cond command failed");
			throw Detection_failed();
		}
		arg = Mmio::read<Rsp0>();
		arg = (voltages & (arg & 0x007FFF80)) | (arg & 0x60000000);

		_delayer.usleep(1000);

		if (Ocr::Busy::get(Mmio::read<Rsp0>()))
			break;
	}
	if (i == 0) {
		error("Send_op_cond timed out, could no power-on SD/MMC card");
		throw Detection_failed();
	}
	Card_info card_info = _detect_mmc();

	/* switch frequency to high speed */
	enum { EXT_CSD_HS_TIMING = 185 };
	if (!issue_command(Mmc_switch(EXT_CSD_HS_TIMING, 1))) {
		error("Error setting high speed frequency");
		throw Detection_failed();
	}
	enum { EXT_CSD_BUS_WIDTH = 183 };
	/* set card to 8 bit */
	if (!issue_command(Mmc_switch(EXT_CSD_BUS_WIDTH, 2))) {
		error("Error setting card bus width");
		throw Detection_failed();
	}
	Mmio::write<Ctype>(BUS_WIDTH_8);

	/* set to eight bit transfer Bit */
	if (!_setup_bus(CLK_DIV_52Mhz)) {
		error("Error setting bus to high speed");
		throw Detection_failed();
	}
	/* Enable IRQs data read timeout, data transfer done, resp error */
	Mmio::write<Intmask>(0x28a);
	Mmio::write<Ctrl::Global_interrupt>(1);
	return card_info;
}


bool Driver::_setup_idmac_descriptor_table(size_t block_count,
                                           addr_t phys_addr)
{
	size_t const max_idmac_block_count = IDMAC_DESC_MAX_ENTRIES * 8;

	if (block_count > max_idmac_block_count) {
		error("Block request too large");
		return false;
	}
	_reset_fifo();

	Idmac_desc::Flags flags = Idmac_desc::FS;
	size_t b = block_count;
	int index = 0;
	for (index = 0; b; index++, phys_addr += 0x1000, flags = Idmac_desc::NONE) {
		b = _idmac_desc[index].set(b, _block_size(), phys_addr, flags);
		_idmac_desc[index].next =
			_idmac_desc_phys + ((index + 1) * sizeof(Idmac_desc));
	}
	_idmac_desc[index].next = (unsigned)_idmac_desc;
	_idmac_desc[index].flags |= Idmac_desc::ER;
	Mmio::write<Dbaddr>(_idmac_desc_phys);

	Mmio::write<Ctrl::Dma_enable>(1);
	Mmio::write<Ctrl::Use_internal_dmac>(1);

	Mmio::write<Bmod::Fixed_burst>(1);
	Mmio::write<Bmod::Idmac_enable>(1);

	Mmio::write<Blksize>(_block_size());
	Mmio::write<Bytcnt>(_block_size() * block_count);

	Mmio::write<Pldmnd>(1);

	return true;
}


void Driver::_handle_irq()
{
	_irq.ack_irq();

	if (!_block_transfer.pending) {
		return; }

	bool success = false;
	if (Mmio::read<Rintsts::Response_error>()) {
		error("Response error");
	}
	if (Mmio::read<Rintsts::Data_read_timeout>()) {
		error("Data read timeout");
	}
	if (Mmio::read<Rintsts::Data_crc_error>()) {
		error("CRC error");
	}
	if (Mmio::read<Rintsts::Data_transfer_over>()) {
		Mmio::write<Rintsts>(~0U);
		if (!_issue_command(Stop_transmission())) {
			error("unable to stop transmission");
		} else {
			success = true;
		}
	}
	_block_transfer.pending = false;
	ack_packet(_block_transfer.packet, success);
}


bool Driver::_issue_command(Command_base const &command)
{
	try { wait_for(Attempts(10000), Microseconds(100), _delayer,
	               Status::Data_busy::Equal(0)); }
	catch (Polling_timeout) {
		error("wait for State::Data_busy timed out ",
		      Hex(Mmio::read<Status>()));
		return false;
	}

	Mmio::write<Rintsts>(~0UL);

	/* write command argument */
	Mmio::write<Cmdarg>(command.arg);

	Cmd::access_t cmd = 0;
	Cmd::Index::set(cmd, command.index);

	if (command.transfer != TRANSFER_NONE) {
		/* set data-direction bit depending on the command */
		bool const write = command.transfer == TRANSFER_WRITE;
		Cmd::Data_expected::set(cmd, 1);
		Cmd::Write::set(cmd, write ? 1 : 0);
	}

	Cmd::access_t rsp_type = 0;
	switch (command.rsp_type) {
	case RESPONSE_NONE:             rsp_type = Cmd::Rsp_type::RESPONSE_NONE;             break;
	case RESPONSE_136_BIT:          rsp_type = Cmd::Rsp_type::RESPONSE_136_BIT;          break;
	case RESPONSE_48_BIT:           rsp_type = Cmd::Rsp_type::RESPONSE_48_BIT;           break;
	case RESPONSE_48_BIT_WITH_BUSY: rsp_type = Cmd::Rsp_type::RESPONSE_48_BIT_WITH_BUSY; break;
	}

	Cmd::Rsp_type::set(cmd, rsp_type);
	Cmd::Start_cmd::set(cmd, 1);
	Cmd::Use_hold_reg::set(cmd ,1);
	Cmd::Wait_prvdata_complete::set(cmd, 1);

	if (command.index == 0)
		Cmd::Init_sequence::set(cmd, 1);

	/* issue command */
	Mmio::write<Cmd>(cmd);

	try { wait_for(Attempts(10000), Microseconds(100), _delayer,
	               Rintsts::Command_done::Equal(1)); }
	catch (Polling_timeout) {
		error("command failed "
		      "Rintst: ", Mmio::read<Rintsts>(), " "
		      "Mintst: ", Mmio::read<Mintsts>(), " "
		      "Status: ", Mmio::read<Status>());

		if (Mmio::read<Rintsts::Response_timeout>())
			warning("timeout");

		if (Mmio::read<Rintsts::Response_error>())
			warning("repsonse error");

		return false;
	}

	/* acknowledge interrupt */
	Mmio::write<Rintsts::Command_done>(1);

	_delayer.usleep(100);

	return true;
}


Cid Driver::_read_cid()
{
	Cid cid;
	cid.raw_0 = Mmio::read<Rsp0>();
	cid.raw_1 = Mmio::read<Rsp1>();
	cid.raw_2 = Mmio::read<Rsp2>();
	cid.raw_3 = Mmio::read<Rsp3>();
	return cid;
}


Csd Driver::_read_csd()
{
	Csd csd;
	csd.csd0 = Mmio::read<Rsp0>();
	csd.csd1 = Mmio::read<Rsp1>();
	csd.csd2 = Mmio::read<Rsp2>();
	csd.csd3 = Mmio::read<Rsp3>();
	return csd;
}


size_t Driver::_read_ext_csd()
{
	Attached_ram_dataspace ds(_env.ram(), _env.rm(), 0x1000, UNCACHED);

	addr_t phys = Dataspace_client(ds.cap()).phys_addr();
	_setup_idmac_descriptor_table(1, phys);

	if (!issue_command(Mmc_send_ext_csd()))
		throw Detection_failed();

	try { wait_for(_delayer, Rintsts::Data_transfer_over::Equal(1)); }
	catch (Polling_timeout) {
		error("cannot retrieve extented CSD");
		throw Detection_failed();
	}
	/* clear IRQ */
	Mmio::write<Rintsts::Data_transfer_over>(1);

	/* contruct extented CSD */
	Ext_csd csd((addr_t)ds.local_addr<addr_t>());

	/* read revision */
	if (csd.Mmio::read<Ext_csd::Revision>() < 2) {
		error("extented CSD revision is < 2");
		throw Detection_failed();
	}

	/* return sector count */
	uint64_t capacity =  csd.Mmio::read<Ext_csd::Sector_count>() * _block_size();

	/* to MB */
	return capacity / (1024 * 1024);
}


size_t Driver::Idmac_desc::set(size_t block_count,
                               size_t block_size,
                               addr_t phys_addr,
                               Flags  flag)
{
	constexpr size_t MAX_BLOCKS = 8;
	flags = OWN | flag |
	        (block_count <= MAX_BLOCKS ? LD : (CH | DIC));
	bytes = ((block_count < MAX_BLOCKS) ? block_count : MAX_BLOCKS) *
	        block_size;
	addr  = phys_addr;

	return block_count < MAX_BLOCKS ?
	       0 : block_count  - MAX_BLOCKS;
}
