/*
 * bus.c
 *
 *  Created on: 9 Aug 2012
 *      Author: daniel
 */

#include <stdint.h>
#include <stddef.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdbool.h>
#include <inttypes.h>
#include "globals.h"
#include "bus.h"

#define GPIO_SYSFSPATH "/sys/class/gpio"
#define GPIO_EXPORTNODE "export"
#define GPIO_UNEXPORTNODE "unexport"
#define GPIO_DIRECTIONNODE "direction"
#define GPIO_VALUENODE "value"
#define GPIO_LABELNODE "label"
#define GPIO_BASENODE "base"

#define GPIO_DIRECTION_IN "in"
#define GPIO_DIRECTION_OUT "out"
#define SIZEOFARRAY(a) (sizeof(a)/sizeof(a[0]))
#define PATHLEN 256

#define GPMC_BASE 0x50000000
#define GPMC_REVISION 0x0
#define GPMC_SYSCONFIG (0x10 / 4)
#define GPMC_SYSSTATUS (0x14 / 4)
#define GPMC_IRQSTATUS (0x18 / 4)
#define GPMC_IRQENABLE (0x1c / 4)
#define GPMC_TIMEOUT_CONTROL (0x40 / 4)
#define GPMC_ERR_ADDRESS (0x44 / 4)
#define GPMC_ERR_TYPE (0x48 / 4)

#define GPMC_CHIPSELECTCONFIGDISPLACEMENT (0x30 / 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 GPMC_SIZE_256MB	0
#define GPMC_SIZE_128MB	0x8
#define GPMC_SIZE_64MB	0xc
#define GPMC_SIZE_32MB	0xe
#define GPMC_SIZE_16MB	0xf

static int devmemfd = -1;

static bool util_isbeaglebone()
{
	int fd = open("/sys/devices/avtech.9/modalias", O_RDONLY);
	close(fd);
	return fd > -1;
}

static void* util_mapmemoryblock(off_t offset, size_t len)
{
	devmemfd = open("/dev/mem", O_RDWR | O_SYNC);
	void* registers = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, devmemfd, offset);
	if (registers == MAP_FAILED) {
		printf("Map failed\n");
	}
	return registers;
}

static void util_unmapmemoryblock(void* block, size_t len)
{
	munmap((void*) block, len);
	if (devmemfd != -1) {
		close(devmemfd);
	}
}

#define REGLEN 0x10000000

static volatile uint32_t* registers = NULL;

static void gpmc_mapregisters()
{
	registers = (uint32_t*) util_mapmemoryblock(GPMC_BASE, REGLEN);
}

static void gpmc_unmapregisters()
{
	util_unmapmemoryblock((void*) registers, REGLEN);
}

static void gpmc_printconfig(int chipselect)
{

	int displacement = GPMC_CHIPSELECTCONFIGDISPLACEMENT * chipselect;
	printf("Config for CS%d\n", chipselect);
	printf("CONFIG_1 [0x%08"PRIx32"]\n", *(registers + displacement + GPMC_CONFIG1));
	printf("CONFIG_2 [0x%08"PRIx32"]\n", *(registers + displacement + GPMC_CONFIG2));
	printf("CONFIG_3 [0x%08"PRIx32"]\n", *(registers + displacement + GPMC_CONFIG3));
	printf("CONFIG_4 [0x%08"PRIx32"]\n", *(registers + displacement + GPMC_CONFIG4));
	printf("CONFIG_5 [0x%08"PRIx32"]\n", *(registers + displacement + GPMC_CONFIG5));
	printf("CONFIG_6 [0x%08"PRIx32"]\n", *(registers + displacement + GPMC_CONFIG6));
	printf("CONFIG_7 [0x%08"PRIx32"]\n", *(registers + displacement + GPMC_CONFIG7));
}

static void gpmc_printinfo()
{

	gpmc_mapregisters();

	uint32_t rev = *(registers + GPMC_REVISION);
	uint8_t major = (rev >> 4) & 0xF;
	uint8_t minor = rev & 0xF;

	printf("GPMC Rev is apparently %"PRId8".%"PRId8"\n", major, minor);
	printf("SYSCONFIG\t[0x%08"PRIx32"]\n", *(registers + GPMC_SYSCONFIG));
	printf("SYSSTATUS\t[0x%08"PRIx32"]\n", *(registers + GPMC_SYSSTATUS));
	printf("IRQSTATUS\t[0x%08"PRIx32"]\n", *(registers + GPMC_IRQSTATUS));
	printf("IRQENABLE\t[0x%08"PRIx32"]\n", *(registers + GPMC_IRQENABLE));
	printf("TIMEOUT_CONTROL\t[0x%08"PRIx32"]\n", *(registers + GPMC_TIMEOUT_CONTROL));
	printf("ERR_ADDRESS\t[0x%08"PRIx32"]\n", *(registers + GPMC_ERR_ADDRESS));
	printf("ERR_TYPE\t[0x%08"PRIx32"]\n", *(registers + GPMC_ERR_TYPE));

	int i;
	for (i = 0; i < 8; i++) {
		gpmc_printconfig(i);
	}

	gpmc_unmapregisters();

}


#define BIDIR 0x20
#define PULL_UP 0x10
#define PULL_DOWN 0x00
#define NO_PULL 0x80
#define MODE_0 0x00
#define MODE_1 0x01
#define MODE_2 0x02
#define MODE_3 0x03
#define MODE_4 0x04
#define MODE_5 0x05
#define MODE_6 0x06
#define MODE_7 0x07


// multiply by 20 ns
#define TOTAL_IO_CYCLE 14  // 280 ns cycle
#define CS_DELAY 1         // CS low 20ns after address valid
#define DELAY_TO_WR_RD 2   // RD/WR lines low 40ns after address valid
#define WR_RD_WIDTH 8     // stay low for until 200 ns after address valid
#define DATA_READY 8     // data ready 160ns after address valid


static void gpmc_setup(void)
{


	int CSWROFFTIME = TOTAL_IO_CYCLE;
	int CSRDOFFTIME = TOTAL_IO_CYCLE;
	int CSONTIME = CS_DELAY;
	int config2 = (CSWROFFTIME << 16) | (CSRDOFFTIME << 8) | CSONTIME;

	int WEOFFTIME = DELAY_TO_WR_RD + WR_RD_WIDTH;
	int WEONTIME = DELAY_TO_WR_RD;
	int OEOFFTIME = DELAY_TO_WR_RD + WR_RD_WIDTH;
	int OEONTIME = DELAY_TO_WR_RD;
	int config4 = (WEOFFTIME << 24) | (WEONTIME << 16) | (OEOFFTIME << 8) | OEONTIME;

	int RDACCESSTIME = DATA_READY;
	int WRCYCLETIME = TOTAL_IO_CYCLE;
	int RDCYCLETIME = TOTAL_IO_CYCLE;
	int config5 = (RDACCESSTIME << 16) | (WRCYCLETIME << 8) | RDCYCLETIME;

	int WRACCESSTIME = DATA_READY;
	int config6 = WRACCESSTIME << 24;

	gpmc_mapregisters();

	if (registers != MAP_FAILED) {
		int chipselect = 0;
		int size = GPMC_SIZE_16MB;
		bool enablecs = true;
		int baseaddress = 1;
		int displacement = GPMC_CHIPSELECTCONFIGDISPLACEMENT * chipselect;

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

		*(registers + displacement + GPMC_CONFIG1) = 0x10; // double all of the timing values
		*(registers + displacement + GPMC_CONFIG2) = config2;
		*(registers + displacement + GPMC_CONFIG3) = 0x0; // not using ADV so we can ignore this guy
		*(registers + displacement + GPMC_CONFIG4) = config4;
		*(registers + displacement + GPMC_CONFIG5) = config5;
		*(registers + displacement + GPMC_CONFIG6) = config6;
		*(registers + displacement + GPMC_CONFIG7) = size << 8 | (enablecs ? 1 << 6 : 0) | baseaddress;

		gpmc_unmapregisters();
	}
}

/*
 * Export a gpio from the kernel to userland
 */

static int gpio_export(unsigned gpio_pin)
{
	FILE* exportfile = fopen(GPIO_SYSFSPATH"/"GPIO_EXPORTNODE, "w");
	if (exportfile != NULL) {
		fprintf(exportfile, "%u\n", gpio_pin);
		fclose(exportfile);
		return 0;
	}
	return -1;
}

/*
 * Un-export a gpio from userland
 */

static int gpio_unexport(unsigned gpio_pin)
{
	FILE* unexportfile = fopen(GPIO_SYSFSPATH"/"GPIO_UNEXPORTNODE, "w");
	if (unexportfile != NULL) {
		fprintf(unexportfile, "%u\n", gpio_pin);
		fclose(unexportfile);
		return 0;
	}
	return -1;
}

/*
 * Change a pins direction
 */

static int gpio_changedirection(unsigned gpio_pin, bool out)
{
	char path[PATHLEN];
	snprintf(path, PATHLEN, GPIO_SYSFSPATH"/gpio%u/"GPIO_DIRECTIONNODE, gpio_pin);
	FILE* directionfile = fopen(path, "w");
	if (directionfile != NULL) {
		if (out) {
			fputs(GPIO_DIRECTION_OUT, directionfile);
		} else {
			fputs(GPIO_DIRECTION_IN, directionfile);
		}

		fclose(directionfile);
		return 0;
	}
	return -1;
}

static void gpio_getvaluenodepath(unsigned gpio_pin, char* buffer)
{
	snprintf(buffer, PATHLEN, GPIO_SYSFSPATH"/gpio%u/"GPIO_VALUENODE, gpio_pin);
}

/*
 * Read a pin's value
 */

static int gpio_readvalue(unsigned gpio_pin)
{
	char path[PATHLEN];
	gpio_getvaluenodepath(gpio_pin, path);
	FILE* valuefile = fopen(path, "r");
	if (valuefile != NULL) {
		char value[2]; // value will be a single char and \n
		fread(value, 1, 2, valuefile);
		fclose(valuefile);
		return atoi(value);
	}
	return -1;
}

/*
 * Write a pin's value
 */

static int gpio_writevalue(unsigned gpio_pin, int value)
{
	char path[PATHLEN];
	gpio_getvaluenodepath(gpio_pin, path);
	FILE* valuefile = fopen(path, "w");
	if (valuefile != NULL) {
		fprintf(valuefile, "%d\n", value);
		fclose(valuefile);
		return 0;
	}
	return -1;
}

// gpio pins are grouped into 4 bunches of 32 pins

#define GPIO0_X (0 * 32)
#define GPIO1_X (1 * 32)
#define GPIO2_X (2 * 32)
#define GPIO3_X (3 * 32)

static unsigned gpio_pins[] = {	GPIO0_X + 22,	// i.e., GPIO0_22
                                GPIO0_X + 23,
                                GPIO0_X + 26,
                                GPIO1_X + 15,	// i.e., GPIO1_15
                                GPIO1_X + 14,
                                GPIO0_X + 27,
                                GPIO1_X + 12,
                                GPIO2_X + 1
                              };


static volatile uint8_t* extbus;

void bus_init()
{

	globals.HWDetect.beaglebone = util_isbeaglebone();

	if (!globals.HWDetect.beaglebone) {
		printf("This doesn't seem to be a beaglebone.. bus stuff disabled!\n");
	} else {
		gpmc_setup();

		extbus = (uint8_t*) util_mapmemoryblock(0x01000000, 0x100);

		//gpmc_printinfo();

		int i;
		for (i = 0; i < SIZEOFARRAY(gpio_pins); i++) {
			// these pins all happen to default to the correct mode (7)
			// so no call to pinmux_configurepin is necessary
			gpio_export(gpio_pins[i]);
			gpio_changedirection(gpio_pins[i], true);
		}

		// this could be more elegant -
		// the POWER_FAIL pin is an input

		gpio_changedirection(gpio_pins[POWER_FAIL], false);

		// reset the TNT4882 chip
		bus_setpin(RST_GPIB, 0);
		g_usleep(10);
		bus_setpin(RST_GPIB, 1);
		g_usleep(10);
	}
}


int bus_getpin(int pin)
{
	if (globals.HWDetect.beaglebone) {
		return gpio_readvalue(gpio_pins[pin]);
	} else {
		return 0;
	}
}

void bus_setpin(int pin, int value)
{
	if (globals.HWDetect.beaglebone) {
		gpio_writevalue(gpio_pins[pin], value & 0x1);
	}
}

void bus_writebyte(uint8_t address, uint8_t data)
{
	if (globals.HWDetect.beaglebone) {
		*(extbus + address) = data;
	}
}

uint8_t bus_readbyte(uint8_t address)
{
	if (globals.HWDetect.beaglebone) {
		return *(extbus + address);
	} else {
		return 0;
	}
}

void bus_shutdown()
{
	if (globals.HWDetect.beaglebone) {
		util_unmapmemoryblock((void*) extbus, 0x100);
		int i;
		for (i = 0; i < SIZEOFARRAY(gpio_pins); i++) {
			gpio_unexport(gpio_pins[i]);

		}
	}
}