/* Optimized SD Library for Teensy 3.X
 * Copyright (c) 2015, Paul Stoffregen, paul@pjrc.com
 *
 * Development of this SD library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing genuine Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#if defined(__arm__)
#include "SD_t3.h"
#ifdef USE_TEENSY3_OPTIMIZED_CODE

uint8_t  SDClass::fat_type;
uint32_t SDClass::fat1_begin_lba;
uint32_t SDClass::fat2_begin_lba;
uint32_t SDClass::data_begin_lba;
uint32_t SDClass::max_cluster;
uint8_t  SDClass::sector2cluster;
File SDClass::rootDir;

static uint32_t unaligned_read32_align16(const void *p)
{
	#ifdef KINETISK
	return *(const uint32_t *)p;
	#else
	return *(const uint16_t *)p | (*(const uint16_t *)(p+1) << 16);
	#endif
}

static uint32_t unaligned_read16_align8(const void *p)
{
	#ifdef KINETISK
	return *(const uint16_t *)p;
	#else
	return *(const uint8_t *)p | (*(const uint8_t *)(p+1) << 8);
	#endif
}

#define BPB_BytsPerSec 11   // 2 bytes
#define BPB_SecPerClus 13   // 1 byte
#define BPB_NumFATs    16   // 1 byte
#define BPB_RootEntCnt 17   // 1 byte
#define BPB_TotSec16   19   // 2 bytes
#define BPB_FATSz16    22   // 2 bytes
#define BPB_TotSec32   32   // 4 bytes
#define BPB_FATSz32    36   // 4 bytes
#define BPB_RootClus   44   // 4 bytes

bool SDClass::begin(uint8_t csPin)
{
	uint8_t status;
	uint32_t cond, hcs, ocr;

	// set up the SPI hardware
	csreg = PIN_TO_BASEREG(csPin);
	csmask = PIN_TO_BITMASK(csPin);
	pinMode(csPin, OUTPUT);
	DIRECT_WRITE_HIGH(csreg, csmask);
	SPI.begin();
	// send clocks to initialize hardware
	SPI.beginTransaction(SD_SPI_SPEED);
	for (uint8_t i=0; i < 5; i++) SPI.transfer16(0xFFFF);
	// put the card into idle state
	elapsedMillis msec = 0;
	while (1) {
		status = sd_cmd0();
		//Serial.print("cmd0=");
		//Serial.println(status);
		if (status == 1) break;
		SPI.endTransaction();
		if (msec > 250) return false;
		SPI.beginTransaction(SD_SPI_SPEED);
	}
	// detect version 1 vs 2 cards
	cond = sd_cmd8();
	if (cond == 0x80000000) {
		// version 1 card
		card_type = 1;
		hcs = 0;
	} else if (cond == 0x1AA) {
		// version 2 card
		card_type = 2;
		hcs = (1<<30);
	} else {
		SPI.endTransaction();
		return false;
	}
	//Serial.println();
	// wait for the card to be ready
	msec = 0;
	while (1) {
		status = sd_acmd41(hcs);
		//Serial.println();
		if (status == 0) break;
		SPI.endTransaction();
		if (status > 1) return false;
		if (msec > 1500) return false;
		SPI.beginTransaction(SD_SPI_SPEED);
	}
	//Serial.println("card is ready");
	// detect high capacity cards
	if (card_type == 2) {
		ocr = sd_cmd58();
		//Serial.print("ocr =");
		//Serial.println(ocr, HEX);
		if ((ocr >> 30) == 3) card_type = 3;
	}
	SPI.endTransaction();
	//Serial.println("init ok");
	// read the MBR (partition table)
	SDCache s;
	sector_t * mbr = s.read(0);
	//Serial.printf(" mbr sig = %04X\n", mbr->u16[255]);
	if (mbr->u16[255] != 0xAA55) return false;
	uint32_t partition_lba = 0;
	uint32_t index = 446;
	do {
		uint8_t type = mbr->u8[index+4];
		//Serial.printf(" partition %d is type %d\n", (index-446)/16+1, type);
		if (type == 6 || type == 11 || type == 12) {
			partition_lba = unaligned_read32_align16(mbr->u8 + index + 8);
			//Serial.printf(" partition lba = %d\n", partition_lba);
			break;
		}
		index += 16;
	} while (index < 64);
	s.release();

	// read the FAT volume ID
	sector_t *vol = s.read(partition_lba);
	if (vol->u16[255] != 0xAA55) return false;
	// BPB_BytsPerSec must be 512 bytes per sector
	if (unaligned_read16_align8(vol->u8 + BPB_BytsPerSec) != 512) return false;
	// BPB_NumFATs must be 2 copies of the file allocation table
	if (vol->u8[BPB_NumFATs] != 2) return false;
	uint32_t reserved_sectors = vol->u16[14/2];
	if (reserved_sectors == 0) return false;
	//Serial.printf(" reserved_sectors = %d\n", reserved_sectors);
	uint32_t sectors_per_cluster = vol->u8[BPB_SecPerClus];
	//Serial.printf(" sectors_per_cluster = %d\n", sectors_per_cluster);
	uint32_t s2c = 31 - __builtin_clz(sectors_per_cluster);
	//Serial.printf(" s2c = %d\n", s2c);
	sector2cluster = s2c;
	uint32_t sectors_per_fat = vol->u16[BPB_FATSz16/2];
	if (sectors_per_fat == 0) sectors_per_fat = vol->u32[BPB_FATSz32/4];
	//Serial.printf(" sectors_per_fat = %d\n", sectors_per_fat);
	uint32_t root_dir_entries = unaligned_read16_align8(vol->u8 + BPB_RootEntCnt);
	//Serial.printf(" root_dir_entries = %d\n", root_dir_entries);
	uint32_t root_dir_sectors = (root_dir_entries + 15) >> 4;
	//Serial.printf(" root_dir_sectors = %d\n", root_dir_sectors);

	uint32_t total_sectors = unaligned_read16_align8(vol->u8 + BPB_TotSec16);
	if (total_sectors == 0) total_sectors = vol->u32[BPB_TotSec32/4];
	//Serial.printf(" total_sectors = %d\n", total_sectors);

	fat1_begin_lba = partition_lba + reserved_sectors;
	fat2_begin_lba = fat1_begin_lba + sectors_per_fat;
	data_begin_lba = fat2_begin_lba + sectors_per_fat + root_dir_sectors;

	uint32_t cluster_count = (total_sectors - reserved_sectors
		- root_dir_sectors - (sectors_per_fat << 1)) >> s2c;
	//Serial.printf(" cluster_count = %d\n", cluster_count);
	max_cluster = cluster_count + 1;
	if (cluster_count < 4085) {
		return false; // FAT12
	} else if (cluster_count < 65525) {
		fat_type = 16;
		rootDir.length = root_dir_entries << 5;
		rootDir.start_cluster = partition_lba + reserved_sectors + sectors_per_fat * 2;
		rootDir.type = FILE_DIR_ROOT16;
	} else {
		fat_type = 32;
		rootDir.length = 0;
		rootDir.start_cluster = vol->u32[BPB_RootClus/4];
		//Serial.printf(" root cluster = %d\n", rootDir.start_cluster);
		rootDir.type = FILE_DIR;
	}
	rootDir.current_cluster = rootDir.start_cluster;
	rootDir.offset = 0;
	s.release();
	//Serial.println(sizeof(fatdir_t));
	return true;
}

#endif
#endif
