#include "stm32f4xx_conf.h"
#include "stm32f4xx.h"

#include "spi_flash.h"
#include "gpio.h"

/*
struct {
	uint8_t *tx;
	uint8_t *rx;
	uint16_t tx_len;
	uint16_t rx_len;
	uint8_t rx_skip;
	TN_SEM ready;
	TN_MUTEX busy;
} xact;

void SPI2_IRQHandler(void) {
	uint32_t status = SPI2->SR;
	uint8_t b;

	if (!xact.tx_len && !xact.rx_len) {
		SPI2->CR1 &= ~SPI_CR1_SPE;
		SPI2->CR2 &= ~(SPI_CR2_TXEIE | SPI_CR2_RXNEIE);
		tn_sem_isignal(&xact.ready);
		return;
	}

	if (status & SPI_SR_RXNE) {
		b = SPI2->DR;
		if (!xact.tx_len) {
			if (xact.rx_skip) {
				xact.rx_skip--;
			}
			else if (xact.rx_len) {
				*(xact.rx++) = b;
				xact.rx_len--;
			}
		}
	}

	if (status & SPI_SR_TXE) {
		if (xact.tx_len) {		// have smth for tx
			SPI2->DR = *(xact.tx++);
			xact.tx_len--;
		}
		else if (xact.rx_len) {
			SPI2->DR = 0;
		}
	}
}
*/

#define SPI_FLASH_CS_L() gpio_set(SPI2_NSS, 0)
#define SPI_FLASH_CS_H() gpio_set(SPI2_NSS, 1)


//static TN_MUTEX spi_mutex;

static uint8_t spi_tx_rx(uint8_t byte) {
	while (!(SPI2->SR & SPI_SR_TXE)) {}
	SPI2->DR = byte;
		
	while (!(SPI2->SR & SPI_SR_RXNE)) {}
	return SPI2->DR;
}

static void spi_cs_up(void) {
	SPI2->CR1 &= ~SPI_CR1_SPE;
//	GPIOB->BSRR = 1 << 12;
}

static void spi_cs_down(void) {
//	GPIOB->BRR = 1 << 12;
	SPI2->CR1 |= SPI_CR1_SPE;
}

static void spi_init_(void) {
//	tn_mutex_create(&spi_mutex, TN_MUTEX_ATTR_INHERIT, 0);
	//tn_sem_create(&xact.ready, 0, 1);

	RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
	RCC->APB1RSTR |= RCC_APB1RSTR_SPI2RST;
	RCC->APB1RSTR &= ~RCC_APB1RSTR_SPI2RST;

	SPI2->CR1 &= ~SPI_CR1_SPE;
	//SPI2->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_SSM | SPI_CR1_SSI;
	SPI2->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI;
	SPI2->CR2 = 0;//SPI_CR2_SSOE;
	SPI2->CR1 |= SPI_CR1_SPE;
}

#define CMD_WREN	0x06
#define CMD_WRDI	0x04
#define CMD_WRSR	0x01
#define CMD_RDSR	0x05
#define CMD_READ	0x03
#define CMD_SE		0x20
#define CMD_BE		0x52
#define CMD_CE		0x60
#define CMD_PP		0x02
#define CMD_RDSFDP	0x5A

#define SR_WIP	(1 << 0)
#define SR_WEL	(1 << 1)
#define SR_SRWD	(1 << 7)

spi_flash_desc_t spi_flash_desc;

static inline void wait_write_enable(void) {
	uint8_t status;
//	spi_cs_down();
	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_RDSR);
	do {
		status = spi_tx_rx(0);
	} while (!(status & SR_WEL));
//	spi_cs_up();
	SPI_FLASH_CS_H();
}

static inline void wait_write_end(void) {
	uint8_t status;
	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_RDSR);
	do {
		status = spi_tx_rx(0);
	} while (status & SR_WIP);
	SPI_FLASH_CS_H();
}

static inline void send_addr(int addr) {
	spi_tx_rx((addr >> 16) & 0xFF);
	spi_tx_rx((addr >> 8) & 0xFF);
	spi_tx_rx(addr & 0xFF);
}

static int spi_flash_read_sfdp(int addr, void *buf, size_t len) {
    uint8_t* foo = (uint8_t *)buf;
    SPI_FLASH_CS_L();
	spi_tx_rx(CMD_RDSFDP);
	send_addr(addr);
	spi_tx_rx(0);
	while (len--)
		//*((uint8_t *)buf++) = spi_tx_rx(0);
      *(foo++) = spi_tx_rx(0);
    SPI_FLASH_CS_H();
	return 0;
}

ssize_t spi_flash_read(int addr, void *buf, size_t len, uint32_t timeout) {
    uint8_t* foo = (uint8_t *)buf;
	ssize_t ret = 0;
//	ret = tn_mutex_lock(&spi_mutex, timeout);
//	if (ret != TERR_NO_ERR)
//		return ret;

	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_READ);
	send_addr(addr);
	while (len--)
		//*((uint8_t *)buf++) = spi_tx_rx(0);
        *(foo++) = spi_tx_rx(0);
	SPI_FLASH_CS_H();
//	tn_mutex_unlock(&spi_mutex);
	return len;
}

#define TIMEOUT 10000

uint16_t spi_flash_pp(int addr, const void *buf, size_t len, uint32_t timeout) {
    uint8_t* foo = (uint8_t *)buf;
//	ret = tn_mutex_lock(&spi_mutex, timeout);
//	if (ret != TERR_NO_ERR)
//		return ret;
	// don't allow page wrapping
	ssize_t ret = 0;
	if ((addr & 0xFF) + len > 0xFF)
	    len = 0x100 - (addr & 0xFF);
	ret = len;

	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_WREN);
	SPI_FLASH_CS_H();

	wait_write_enable();

	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_PP);
	send_addr(addr);
	while (len--)
	    //spi_tx_rx(*((uint8_t *)buf++));
        spi_tx_rx(*(foo++));
	SPI_FLASH_CS_H();

	wait_write_end();

//	tn_mutex_unlock(&spi_mutex);
	return ret;
}

ssize_t spi_flash_write(int addr, const void *buf, size_t len, uint32_t timeout) {
    uint8_t* foo = (uint8_t *)buf;
	int ret = 0, offset = 0;
	do {
	    //ret = spi_flash_pp(addr + offset, buf + offset, len - offset, 0);
        ret = spi_flash_pp(addr + offset, foo + offset, len - offset, 0);
	    offset += ret;
	} while (len - offset);
	return 0;
}

int spi_flash_chip_erase(uint32_t timeout) {
	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_WREN);
	SPI_FLASH_CS_H();
	wait_write_enable();

    	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_CE);
	SPI_FLASH_CS_H();

	wait_write_end();
	return 0;
}

int spi_flash_erase_sector(int addr, uint32_t timeout) {
	int ret = 0;
//	ret = tn_mutex_lock(&spi_mutex, timeout);
//	if (ret != TERR_NO_ERR)
//		return ret;

	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_WREN);
	SPI_FLASH_CS_H();

	wait_write_enable();

	SPI_FLASH_CS_L();
	spi_tx_rx(CMD_SE);
	send_addr(addr);
	SPI_FLASH_CS_H();

	wait_write_end();

//	tn_mutex_unlock(&spi_mutex);
	return 0;
}

bool spi_flash_init(void) {
	uint32_t i, ptable, bitsize = 0;
	uint8_t tmp[4];

	spi_flash_desc.present = false;

	spi_init_();

	// check SFDP magic
	spi_flash_read_sfdp(0, tmp, 4);
	if (!(tmp[0] == 0x53 && tmp[1] == 0x46 && 
		tmp[2] == 0x44 && tmp[3] == 0x50))
		return 0;

	// get parameter headers count
	spi_flash_read_sfdp(0x06, tmp, 1);

	// find first jedec pheader (with ID == 0)
	for (ptable = 0x08, i = 0; i <= tmp[0]; i++, ptable += 8) {
		spi_flash_read_sfdp(ptable, tmp, 1);
		if (tmp[0] == 0)
			break;
	}

	// read ptable pointer from pheader
	spi_flash_read_sfdp(ptable + 4, &ptable, 3);

	// get flash density (size in bits)
	if (spi_flash_read_sfdp(ptable + 4, &bitsize, 4) < 0 || !bitsize)
		return 0;

	// find smallest available sector
	for (i = 0; i < 4; i++) {
		tmp[0] = 0;
		if (spi_flash_read_sfdp(ptable + 0x1C + i*2, &tmp, 2) >= 0 &&
				tmp[0]) {
			spi_flash_desc.sector_size = 1 << tmp[0];
			spi_flash_desc.sector_erase_op = tmp[1];
			spi_flash_desc.sector_count = (bitsize + 1) >> (3 + tmp[0]);
			break;
		}
	}
	if (!spi_flash_desc.sector_size)
		return 0;

	spi_flash_desc.present = true;
    
    return 1;

}

uint8_t txbuf1[] = "This film came out on DVD yesterday and I rushed to buy it. \
		    This version is the first to render all the detail and perfection \
		    of Jack Cardiff's amazing compositions and brilliant, varied photography. \
		    As a collection of memorable images, this film is better than any comparable \
		    historical epic of the period and even gives GWTW a run for its money. \
		    King Vidor's direction is a series of 'tableaux vivants' where the \
		    characters are not posing but acting in a very natural, period-specific way. \
		    I have never had a problem with this adaptation of Tolstoy's novel. \
		    I think it is a wonderful introduction to the period and the novel and that \
		    it is a very poetic, very original work in its own right. Henry Fonda's \
		    characterization is especially moving, including great memorable interactions \
		    with/reations to Mel Ferrer, Audrey Hepburn, Helmut Dantine and John Mills, but \
		    all members of the cast are actually perfect. The harrowing last 45 minutes of \
		    the film manage to convey a sense of history, a sense of grandeur as well as to \
		    communicate very clearly Tolstoy's ideas about the meaning of life, by relying \
		    mostly on the power of memorable images. The most conspicuous handicap of this movie, \
		    in my opinion, is its soundtrack (in glorious mono). The barely hi-fi recording of \
		    dialogues and music sounds pinched, hollow and tinny and it always has in very version \
		    I have ever seen: in the theatres, on TV and on video. Even the soundtrack album is \
		    an atrocity. In some scenes, before the necessary adjustments of bass and treble, \
		    Audrey Hepburn's and Mel Ferrer's voices actually hurt your ear. Nino Rota's very \
		    Russian-sounding score is serviceable and melodic, although rather sparse in its \
		    orchestration and in the number of players. One can only wonder what 'War and Peace' \
		    could have sounded like with a cohort of Hollywood arrangers, decent recording facilities \
		    and lavish, varied orchestrations in true high fidelity and stereophonic sound. \
		    According to Lukas Kendall of 'Film Score Monthly', the original recording elements \
		    of the soundtrack have long ago disappeared, which is the common lot of international, \
		    independent co-productions of the era. Someone somewhere is certainly guilty of \
		    skimping on quality or embezzlement for this 1956 movie to sound so much worse \
		    than a 1939, pre-hi-fi epic like GWTW. Like all VistaVision films, this one was \
		    meant to be shown in Perspecta Stereophonic Sound where the mono dialog track was \
		    meant to be channelled to three different directions, making it directional, while \
		    the separate mono music + sound effects track was generally directed to all three \
		    speakers at the same time. The results fooled the viewers into thinking everything \
		    was in true stereo and the reproduction of the music was usually in very high fidelity. \
		    Maybe the soundtrack used on the DVD is a mono reduction of those two separate tracks \
		    that has squandered that fidelity and maybe the DVD can be issued again with better \
		    results in some kind of 4.0 presentation. When they do, very little electronic \
		    restoration work will be needed to make the image absolutely perfect. \
		    But let's concentrate on the positive: This film is a summit of visual \
		    splendour and its sets, costumes, colour photography, composition and lighting \
		    achieve, in every single scene, wonders of artistry, creativity and delicacy \
		    that will probably never be equalled. Suffice it to say that it has, \
		    among many other treasures, a sunrise duel scene in the snow that still has viewers \
		    wondering whether it was shot outdoors or in a studio and that will have them wondering forever.\r\n";

uint8_t txbuf2[] = "STM32F4xx SPI Firmware Library Example: communication with an M25P SPI FLASH\r\n";

#define countof(a) (sizeof(a) / sizeof(*(a)))
#define  bufsize1 (countof(txbuf1)-1)
#define  bufsize2 (countof(txbuf2)-1)

uint8_t  rxbuf1[bufsize1] = {0};
uint8_t  rxbuf2[bufsize2] = {0};

void spi_flash_test(void) {
    if (!spi_flash_init())
	return;
    int ret = 0, addr = 0, offset = 0;

    spi_flash_read(0, rxbuf1, bufsize1, 0);
    spi_flash_erase_sector(0,0);
    spi_flash_read(0, rxbuf1, bufsize1, 0);
    
    spi_flash_write(addr, txbuf1, bufsize1, 0);

    memset(rxbuf1, 0, bufsize1);
    spi_flash_read(0, rxbuf1, bufsize1, 0);

    if (memcmp(txbuf1, rxbuf1, bufsize1) != 0)
	return;

    spi_flash_erase_sector(0,0);
    spi_flash_read(0, rxbuf1, bufsize1, 0);

    spi_flash_write(0, txbuf2, bufsize2, 0);

    memset(rxbuf2, 0, bufsize2);
    spi_flash_read(0, rxbuf2, bufsize2, 0);


/*
 * TODO restore spi_flash_init();
	spi_flash_init();
	if (!spi_flash_desc.present)
		return -1;
	printf("\tPresent: %d sectors, %d bytes per sector (%d bytes total)\n",
		spi_flash_desc.sector_count,
		spi_flash_desc.sector_size,
		spi_flash_desc.sector_size * spi_flash_desc.sector_count);
	return 0;
*/
    
}
