/*
 * Remote Laboratory FPGA Server GPMC Interface (Beaglebone Black)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * (c) 2012-2014 Timothy Pearson
 * Raptor Engineering
 * http://www.raptorengineeringinc.com
 */

/** BEGIN: Low-Level I/O Implementation **/

// Beaglebone Black GPMC driver

#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#define MEMORY_SPACE_ADDRESS_BITS 16

#define GPMC_BASE 0x50000000
#define GPMC_REGLEN 0x10000000

#define GPMC_CHIPSELECTCONFIGDISPLACEMENT (0x30 / 4)

#define GPMC_CONFIG (0x50 / 4)
#define GPMC_CONFIG1 (0x60 / 4)
#define GPMC_CONFIG2 (0x64 / 4)
#define GPMC_CONFIG3 (0x68 / 4)
#define GPMC_CONFIG4 (0x6c / 4)
#define GPMC_CONFIG5 (0x70 / 4)
#define GPMC_CONFIG6 (0x74 / 4)
#define GPMC_CONFIG7 (0x78 / 4)

#define MEMORY_SIZE (1 << MEMORY_SPACE_ADDRESS_BITS)

int mem_fd = 0;
int gpmc_mem_fd = 0;
char *gpio_mem, *gpio_map, *gpmc_map;

// I/O access
volatile unsigned int *gpio = NULL;
volatile unsigned char *gpio_char = NULL;
volatile unsigned char *gpio_llong = NULL;
volatile unsigned int *gpmc = NULL;

void gpmc_mapregisters() {
	/* open /dev/mem */
	if ((gpmc_mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
		printf("[FATAL] can't open /dev/mem\n");
		return;
	}

	/* mmap GPMC */
	gpmc_map = (char *)mmap(
		0,
		GPMC_REGLEN,
		PROT_READ|PROT_WRITE,
		MAP_SHARED,
		gpmc_mem_fd,
		GPMC_BASE
	);

	if (gpmc_map == MAP_FAILED) {
		printf("[FATAL] mmap error %d\n", (int)gpmc_map);
		return;
	}

	// Always use volatile pointer!
	gpmc = (volatile unsigned *)gpmc_map;
}

void gpmc_unmapregisters() {
	munmap((void*) gpmc, GPMC_REGLEN);
	if (gpmc_mem_fd != -1) {
		close(gpmc_mem_fd);
	}
}

void gpmc_setup(void) {
	gpmc_mapregisters();

	if (gpmc != NULL) {
		int chipselect = 0;
		int displacement = GPMC_CHIPSELECTCONFIGDISPLACEMENT * chipselect;

		// disable before playing with the registers
		*(gpmc + displacement + GPMC_CONFIG7) = 0x00000000;

// 		*(gpmc + displacement + GPMC_CONFIG)  = 0x00000000; // Unlimited address space
// 		*(gpmc + displacement + GPMC_CONFIG1) = 0x00000000; // No burst, async, 8-bit, non multiplexed
// 		*(gpmc + displacement + GPMC_CONFIG2) = 0x00001000; // Assert CS on fclk0, deassert CS on fclk16
// 		*(gpmc + displacement + GPMC_CONFIG3) = 0x00000400; // Assert ADV on fclk 0, deassert ADV on fclk 4
// 		*(gpmc + displacement + GPMC_CONFIG4) = 0x0c041004; // Assert WE on fclk4, deassert WE on fclk12, assert OE on fclk4, deassert OE on fclk16
// 		*(gpmc + displacement + GPMC_CONFIG5) = 0x000c1010; // Data valid on fclk 12, cycle time 16 fclks
// 		*(gpmc + displacement + GPMC_CONFIG6) = 0x00000000; // No back to back cycle restrictions
// 		*(gpmc + displacement + GPMC_CONFIG7) = 0x00000e50; // CS0: Set base address 0x10000000, 32MB region, and enable CS

		// Use slower clocking to reduce errors in wire nest prototype
		*(gpmc + displacement + GPMC_CONFIG)  = 0x00000000; // Unlimited address space
		*(gpmc + displacement + GPMC_CONFIG1) = 0x00000000; // No burst, async, 8-bit, non multiplexed
		*(gpmc + displacement + GPMC_CONFIG2) = 0x00001f00; // Assert CS on fclk0, deassert CS on fclk31
		*(gpmc + displacement + GPMC_CONFIG3) = 0x00000400; // Assert ADV on fclk 0, deassert ADV on fclk 4
		*(gpmc + displacement + GPMC_CONFIG4) = 0x1f041f04; // Assert WE on fclk4, deassert WE on fclk31, assert OE on fclk4, deassert OE on fclk31
		*(gpmc + displacement + GPMC_CONFIG5) = 0x00101f1f; // Data valid on fclk 16, cycle time 31 fclks
		*(gpmc + displacement + GPMC_CONFIG6) = 0x00000000; // No back to back cycle restrictions
		*(gpmc + displacement + GPMC_CONFIG7) = 0x00000e50; // CS0: Set base address 0x10000000, 32MB region, and enable CS

		gpmc_unmapregisters();
	}
}

int setup_gpmc_bbb(void) {
	/* open /dev/mem */
	if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
		printf("[FATAL] can't open /dev/mem\n");
		return -1;
	}

	/* mmap GPIO */
	gpio_map = (char *)mmap(
		0,
		MEMORY_SIZE,
		PROT_READ|PROT_WRITE,
		MAP_SHARED,
		mem_fd,
		0x10000000
	);

	if (gpio_map == MAP_FAILED) {
		printf("[FATAL] mmap error %d\n", (int)gpio_map);
		return -1;
	}

	// Always use volatile pointers!
	gpio = (volatile unsigned *)gpio_map;
	gpio_char = (volatile unsigned char *)gpio_map;

	return 0;
}

int shutdown_gpmc_bbb(void) {
	return 0;
}

void write_gpmc(unsigned int register_offset, unsigned char data) {
	*(gpio_char + register_offset) = data;
}

unsigned char read_gpmc(unsigned int register_offset) {
	return *(gpio_char + register_offset);
}

void write_gpmc_llong(unsigned int register_offset, unsigned long long data) {
	*(gpio_char + register_offset) = data;
}

unsigned long long read_gpmc_llong(unsigned int register_offset) {
	return *(gpio_char + register_offset);
}

void memcpy_from_gpmc(char* destination, unsigned int register_offset, unsigned int length) {
	unsigned int i;
	for (i=0; i<length; i++) {
		*destination = *(gpio_char + register_offset);
		destination++;
		register_offset++;
	}
}

void memcpy_to_gpmc(char* source, unsigned int register_offset, unsigned int length) {
	unsigned int i;
	for (i=0; i<length; i++) {
		*(gpio_char + register_offset) = *source;
		source++;
		register_offset++;
	}
}

/** END: Low-Level I/O Implementation **/