#include <string.h>
#include <avr/io.h>

#include "mmc.h"
#include "mmc_def.h"

/**
 *
 * Модуль реализует низкоуровневые функции чтения карт памяти MMC, SD и SDHC.
 *
 */


	
// код типа карты
static uint8_t mmc_card_type;

// вспомогательные функции
static void mmc_send_byte(uint8_t b);
static uint8_t mmc_rec_byte();
static uint8_t mmc_send_command(uint8_t command, uint32_t arg);


/**
 * Послать байт интерфейсу карты памяти.
 *
 * \param[in] b байт, который будет послан.
  */
void mmc_send_byte(uint8_t b) {
	SPDR = b;
	// ожидание передачи байта
	while ( !(SPSR & (1 << SPIF)) );
	SPSR &= ~(1 << SPIF);
}

/**
 * Прочитать байт из интерфейса карты памяти.
 *
 * \returns прочитанный байт.
 */
uint8_t mmc_rec_byte() {
//	mmc_send_byte(0xFF);
	// send dummy data for receiving some
	SPDR = 0xff;
	while(!(SPSR & (1 << SPIF)));
	SPSR &= ~(1 << SPIF);

	return SPDR;
}


/**
 * Отправка команды интерфейсу карты
 *
 * \param[in] command команда для отправки.
 * \param[in] arg аргумент команды.
 * \returns ответ карты памяти, результат выполнения команды.
 */
uint8_t mmc_send_command(uint8_t command, uint32_t arg) {
	uint8_t response;

	// посылка 8 тактовых импульсов
	mmc_rec_byte();

	// посылаем команду по SPI
	mmc_send_byte(0x40 | command);
	mmc_send_byte((arg >> 24) & 0xff);
	mmc_send_byte((arg >> 16) & 0xff);
	mmc_send_byte((arg >> 8) & 0xff);
	mmc_send_byte((arg >> 0) & 0xff);
	switch ( command ) {
		case CMD_GO_IDLE_STATE:
			mmc_send_byte(0x95);
			break;
		case CMD_SEND_IF_COND:
			mmc_send_byte(0x87);
			break;
		default:
			mmc_send_byte(0xff);
			break;
	}
    
	// читаем ответ
	for ( uint8_t i = 0; i < 10; ++i ) {
		response = mmc_rec_byte();
		if ( response != 0xff )
			break;
	}

    return response;
}




	/**
	 * Иннициализировать соединение с картой памяти.
	 *
	 * \returns 0 - если операция прошла успешно, иначе код ошибки.
	 */
	uint8_t mmc_init() {
		// включаем режим вывода для MOSI, SCK, SS и ввода для MISO
		configure_pin_mosi();
		configure_pin_sck();
		configure_pin_ss();
		configure_pin_miso();

		unselect_card();

		// иннициализировать SPI с минимальной частотой, не более 400kHz для 
		// идентификации режима карты
		SPCR = (0 << SPIE) |			// SPI Interrupt Enable
					(1 << SPE)  |		// SPI Enable
					(0 << DORD) |		// Data Order: MSB first
					(1 << MSTR) |		// Master mode
					(0 << CPOL) |		// Clock Polarity: SCK low when idle
					(0 << CPHA) |		// Clock Phase: sample on rising SCK edge
					(1 << SPR1) |		// Clock Frequency: f_OSC/128
					(1 << SPR0);
		SPSR &= ~(1 << SPI2X);		// No doubled clock frequency

		// сбрасываем тип карты
		mmc_card_type = 0;
    

		// посылаем 80 тактовых импульсов (надо минимум 74 импульса) для иннициализации
		for ( uint8_t i = 0; i < 10; ++i )
			mmc_rec_byte();		// 8 тактовых импульсов

	 	// адресация карты
		select_card();

	 	// сброс карты
		uint8_t response;
		for ( uint16_t i = 0; ; ++i ) {
			response = mmc_send_command(CMD_GO_IDLE_STATE, 0);
			if ( response == (1 << R1_IDLE_STATE) )
				break;
			if ( i == 0x1ff ) {
				unselect_card();
				return 1;
			}
		}



	#if MMC_SDHC
		// проверка версии спецификации для SD-карты
		response = mmc_send_command(CMD_SEND_IF_COND, 0x100 /* 2.7V - 3.6V */ | 0xaa /* test pattern */);
		if ( (response & (1 << R1_ILL_COMMAND)) == 0 ) {
			mmc_rec_byte();
			mmc_rec_byte();
			if ( (mmc_rec_byte() & 0x01) == 0 )
				return 2; // неверный диапазон напряжения питания
			if ( mmc_rec_byte() != 0xaa )
				return 3; // wrong test pattern

			// карта соответствует спецификации SD2 
			mmc_card_type |= (1 << MMC_SPEC_2);
		} else
	#endif
		{
			// определение типа карты SD/MMC
			mmc_send_command(CMD_APP, 0);
			response = mmc_send_command(CMD_SD_SEND_OP_COND, 0);
			if ( (response & (1 << R1_ILL_COMMAND)) == 0 ) {
				// карта соответствует спецификации SD1
				mmc_card_type |= (1 << MMC_SPEC_1);
			} else  {
				// карта MMC
			}
		}


		// ожидание готовности карты
		for ( uint16_t i = 0; ; ++i ) {
			if ( mmc_card_type & ((1 << MMC_SPEC_1) | (1 << MMC_SPEC_2)) ) {
				uint32_t arg = 0;
	#if MMC_SDHC
				if ( mmc_card_type & (1 << MMC_SPEC_2) )
					arg = 0x40000000;
	#endif
				mmc_send_command(CMD_APP, 0);
				response = mmc_send_command(CMD_SD_SEND_OP_COND, arg);
			} else {
				response = mmc_send_command(CMD_SEND_OP_COND, 0);
			}

			if ( (response & (1 << R1_IDLE_STATE)) == 0 )
				break;

			if ( i == 0x7fff ) {
				unselect_card();
				return 4;
			}
		}

	#if MMC_SDHC
		if ( mmc_card_type & (1 << MMC_SPEC_2) ) {
			if ( mmc_send_command(CMD_READ_OCR, 0) ) {
				unselect_card();
				return 5;
			}

			if ( mmc_rec_byte() & 0x40 )
				mmc_card_type |= (1 << MMC_SPEC_SDHC);

			mmc_rec_byte();
			mmc_rec_byte();
			mmc_rec_byte();
		}
	#endif // MMC_SDHC


		// установить размер блока в 512 байт
		if ( mmc_send_command(CMD_SET_BLOCKLEN, BLOCK_SIZE) ) {
			unselect_card();
			return 6;
		}

		// деадрессация карты
		unselect_card();

		// переключение на максимально возможную частоту SPI
		SPCR &= ~((1 << SPR1) | (1 << SPR0));	// частота f_OSC / 4
		SPSR |= (1 << SPI2X); 						// удвоенная частота f_OSC / 2

		return 0;
	}





#if MMC_SUPPORT_GET_INFO
	/**
	 * Получение сведений о карте.
	 *
	 * Функция читает и возвращает содержимое регистров карты с информацией о производителе
	 * и характеристиках
	 *
	 *
	 * \param[in] info указатель на структуру, в которую будет сохранена информация.
	 * \returns 0 в случае успеха, иначе код ошибки.
	 */
	uint8_t mmc_get_info(struct mmc_info* info) {
		memset(info, 0, sizeof(*info));

		select_card();

		// чтение регистра cid
		if ( mmc_send_command(CMD_SEND_CID, 0) ) {
			unselect_card();
			return 1;
		}

		while ( mmc_rec_byte() != 0xfe );

		for ( uint8_t i = 0; i < 18; ++i ) {
			uint8_t b = mmc_rec_byte();

			if ( i == 0 )
				info->manufacturer = b;
			else if ( i <= 2 )
				info->oem[i - 1] = b;
			else if ( i <= 7 )
				info->product[i - 3] = b;
			else if ( i == 8 )
				info->revision = b;
			else if ( i <= 12 )
				info->serial |= (uint32_t) b << ((12 - i) * 8);
			else if ( i == 13 )
				info->manufacturing_year = b << 4;
			else if ( i == 14 ) {
				info->manufacturing_year |= b >> 4;
				info->manufacturing_month = b & 0x0f;
			}

		}

		// чтение регистра csd
		uint8_t csd_read_bl_len = 0;
		uint8_t csd_c_size_mult = 0;
	#if MMC_SDHC
		uint16_t csd_c_size = 0;
	#else
		uint32_t csd_c_size = 0;
	#endif
		if ( mmc_send_command(CMD_SEND_CSD, 0) ) {
			unselect_card();
			return 2;
		}
		while ( mmc_rec_byte() != 0xfe );

		for ( uint8_t i = 0; i < 18; ++i ) {
			uint8_t b = mmc_rec_byte();

			if ( i == 14 ) {
				if ( b & 0x40 )
					info->flag_copy = 1;
				if ( b & 0x20 )
					info->flag_write_protect = 1;
				if ( b & 0x10 )
					info->flag_write_protect_temp = 1;
				info->format = (b & 0x0c) >> 2;
			}  else {
	#if MMC_SDHC
				if ( mmc_card_type & (1 << MMC_SPEC_2) ) {
					switch ( i ) {
						case 7:
							b &= 0x3f;
						case 8:
						case 9:
							csd_c_size <<= 8;
							csd_c_size |= b;
							break;
					}
					if ( i == 9 ) {
						++csd_c_size;
						info->capacity = (offset_t) csd_c_size * BLOCK_SIZE * 1024;
					}
				} else
	#endif
				{
					switch ( i ) {
						case 5:
							csd_read_bl_len = b & 0x0f;
							break;
						case 6:
							csd_c_size = b & 0x03;
							csd_c_size <<= 8;
							break;
						case 7:
							csd_c_size |= b;
							csd_c_size <<= 2;
							break;
						case 8:
							csd_c_size |= b >> 6;
							++csd_c_size;
							break;
						case 9:
							csd_c_size_mult = b & 0x03;
							csd_c_size_mult <<= 1;
							break;
						case 10:
							csd_c_size_mult |= b >> 7;
							info->capacity = (uint32_t) csd_c_size << (csd_c_size_mult + csd_read_bl_len + 2);
							break;
					} // switch
				} // else
			} // if ( i != 14 )
		} // for

		unselect_card();

		return 0;
	}
#endif



#if MMC_SUPPORT_READ_BLOCK
	/**
	 * Чтение данных с карты.
	 *
	 * \param[in] offset смещение с которого читать данные.
	 * \param[out] buffer буфер в который данные будут сохранены.
	 * \param[in] length количество байт для чтения.
	 * \returns 0 в случае успеха иначе код ошибки.
	 */
	uint8_t mmc_read_block(offset_t offset, uint8_t* buffer, uintptr_t length) {
		uint32_t block_address;
		uint16_t block_offset;
		uint16_t read_length;

		while ( length > 0 ) {
			// определения количества байт для чтения за раз
			block_address = offset / BLOCK_SIZE;		// номер блока
			block_offset = offset & (BLOCK_SIZE-1);	// 0x01ff смещение начальной позиции в этом блоке
			read_length = BLOCK_SIZE - block_offset;	// количество байт, которые осталось прочитать в этом блоке
			if ( read_length > length )
				read_length = length;
        
			// адресуем карту
			select_card();

			// посылаем запрос на чтение одного блока
	#if MMC_SDHC
			if ( mmc_send_command(CMD_READ_SINGLE_BLOCK, ((mmc_card_type & MMC_SPEC_SDHC) ? block_address : block_address * BLOCK_SIZE)) ) {
	#else
			if( mmc_send_command(CMD_READ_SINGLE_BLOCK, block_address * BLOCK_SIZE) ) {
	#endif
				unselect_card();
				return 1;
			}

			// ожидаем блока данных (сразу после байта 0xFE)
			while ( mmc_rec_byte() != 0xFE );

			// читаем блок данных
			uint16_t read_to = block_offset + read_length;
			for ( uint16_t i = 0; i < BLOCK_SIZE; ++i ) {
				uint8_t b = mmc_rec_byte();
				if ( i >= block_offset && i < read_to )
					*buffer++ = b;
			}
            
			// вычитываем CRC16 после данных
			mmc_rec_byte();
			mmc_rec_byte();
            
			// деадрессуем карту
			unselect_card();

			// let card some time to finish
			mmc_rec_byte();

			length -= read_length;
			offset += read_length;
		}

		return 0;
	}
#endif



#if MMC_SUPPORT_READ_BYTE
	static uint32_t read_block_address;			// номер текущего блока, откуда мы читаем
	static uint16_t read_block_offset;			// смещение в текущем блоке
	static uint16_t read_length;					// сколько байт в текущем блоке уже прочитано
	static uint8_t read_error;						// код последней ошибки или 0, если ошибок не возникало



	static void mmc_read_complete_block();

	/**
	 * Иннициализизация процесса последовательного побайтового чтения карты.
	 */
	void mmc_read_init() {
		read_error = 0;
		read_length = 0;
		read_block_address = 0xFFFFFFFF;
		read_block_offset = BLOCK_SIZE;
	}



	/**
	 * Завершение процесса последовательного чтения карты.
	 */
	void mmc_read_done() {
		// если позиционирование проводилось 
		if ( read_block_address != 0xFFFFFFFF ) {
			// вычитываем текущий блок до конца
			mmc_read_complete_block();
			// деадрессуем карту
			unselect_card();
			// let card some time to finish
			mmc_rec_byte();
		}
	}



	/**
	 * Указание позиции чтения.
	 *
	 * \param[in] offset смещение от начала с которого будет прочитан следующий байт.
	 */
	void mmc_read_seek(offset_t offset) {
		// если предыдущий блок не был вычитан до конца, делаем это
		if ( read_block_offset < BLOCK_SIZE ) 
			mmc_read_complete_block();

		// определения количества байт для чтения за раз
		read_block_address = offset / BLOCK_SIZE;			// номер блока
		read_block_offset = offset & (BLOCK_SIZE-1);		// смещение начальной позиции в этом блоке
		read_length = BLOCK_SIZE - read_block_offset;	// количество байт, которые осталось прочитать в этом блоке
        
		// адресуем карту
		select_card();

			// посылаем запрос на чтение одного блока
	#if MMC_SDHC
		if ( mmc_send_command(CMD_READ_SINGLE_BLOCK, ((mmc_card_type & MMC_SPEC_SDHC) ? read_block_address : read_block_address * BLOCK_SIZE)) ) {
	#else
		if( mmc_send_command(CMD_READ_SINGLE_BLOCK, read_block_address * BLOCK_SIZE) ) {
	#endif
			unselect_card();
			read_error = 1;
			return;
		}

		// ожидаем блока данных (сразу после байта 0xFE)
		while ( mmc_rec_byte() != 0xFE );

		// если надо спозиционироваться не на первый байт блока, то вычитываем первые байты
		for ( uint16_t i = 0; i < read_block_offset; i++ ) 
			mmc_rec_byte();
	}



	/**
	 * Читает следующий байт и инкрементирует указатель.
	 *
	 * \returns прочитанный байт.
	 */
	uint8_t mmc_read_get_byte() {
		// если достигнут конец блока, то надо читать следующий
		if ( read_block_offset >= BLOCK_SIZE ) 
			mmc_read_seek((read_block_address+1)*BLOCK_SIZE);
		read_block_offset++;
		return mmc_rec_byte();
	}



	/**
	 * Вычитать текущий блок до конца.
	 */
	void mmc_read_complete_block() {
		// вычитываем конец блока
		for ( uint16_t i = read_block_offset; i < BLOCK_SIZE; i++ ) 
			mmc_rec_byte();
		// вычитываем CRC16 после данных
		mmc_rec_byte();
		mmc_rec_byte();
		read_block_offset = BLOCK_SIZE;
	}



	/**
	 * Читает следующее слово и инкрементирует указатель.
	 *
	 * \returns прочитанное слово.
	 */
	uint16_t mmc_read_get_word() {
		return mmc_read_get_byte() | (mmc_read_get_byte() << 8);
	}



	/**
	 * Читает следующее двойное слово и инкрементирует указатель.
	 *
	 * \returns прочитанное двойное слово.
	 */
	uint32_t mmc_read_get_dword() {
		uint32_t w1 = mmc_read_get_word();
		uint32_t w2 = mmc_read_get_word();		
//		w1 |= w2 << 16;
		return w1 | (w2 << 16);
	}
#endif



