/*
 * 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 "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/kernel/debug/omap_mux/board/core", 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);
	}
}

static void pinmux_configurepin(char* pin, int options_and_mode)
{
	char path[256];
	snprintf(path, sizeof(path), "/sys/kernel/debug/omap_mux/%s", pin);
	FILE* node = fopen(path, "w");
	if (node != NULL) {
		fprintf(node, "0x%02x\n", (options_and_mode & 0xff));
		fclose(node);
	} else {
		printf("Failed to configure pin %s (%s)\n", pin, path);
	}
}

#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();

}

// The shift amounts

#define CSWROFFTIME 16
#define CSRDOFFTIME 8
#define OEOFFTIME 8
#define RDACCESSTIME 16
#define WRCYCLETIME 8
#define RDCYCLETIME 0
#define WRACCESSTIME 24

#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


static void gpmc_setup(int chipselect, int accesscycles, int size, bool enablecs, int baseaddress)
{

	pinmux_configurepin("gpmc_csn0", 	PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_oen_ren", 	PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_wen", 	PULL_UP | MODE_0);

	pinmux_configurepin("gpmc_ad7", BIDIR | PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_ad6", BIDIR | PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_ad5", BIDIR | PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_ad4", BIDIR | PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_ad3", BIDIR | PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_ad2", BIDIR | PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_ad1", BIDIR | PULL_UP | MODE_0);
	pinmux_configurepin("gpmc_ad0", BIDIR | PULL_UP | MODE_0);

	pinmux_configurepin("lcd_data0", PULL_UP | MODE_1);
	pinmux_configurepin("lcd_data1", PULL_UP | MODE_1);
	pinmux_configurepin("lcd_data2", PULL_UP | MODE_1);
	pinmux_configurepin("lcd_data3", PULL_UP | MODE_1);
	pinmux_configurepin("lcd_data4", PULL_UP | MODE_1);
	pinmux_configurepin("lcd_data5", PULL_UP | MODE_1);
	pinmux_configurepin("lcd_data6", PULL_UP | MODE_1);
	pinmux_configurepin("lcd_data7", PULL_UP | MODE_1);

	int displacement = GPMC_CHIPSELECTCONFIGDISPLACEMENT * chipselect;

	gpmc_mapregisters();

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

		*(registers + displacement + GPMC_CONFIG1) = 0x0;
		*(registers + displacement + GPMC_CONFIG2) = (accesscycles << CSWROFFTIME) | (accesscycles << CSRDOFFTIME);
		*(registers + displacement + GPMC_CONFIG3) = 0x0; // not using ADV so we can ignore this guy
		*(registers + displacement + GPMC_CONFIG4) = (accesscycles << OEOFFTIME);
		*(registers + displacement + GPMC_CONFIG5) = (accesscycles << RDACCESSTIME) | (accesscycles << WRCYCLETIME)
		                | (accesscycles << RDCYCLETIME);
		*(registers + displacement + GPMC_CONFIG6) = (accesscycles << WRACCESSTIME);
		*(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, "%d\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, "%d\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%d/"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%d/"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
                              };


static volatile uint8_t* extbus;
static bool isbb = false;

void bus_init()
{

	isbb = util_isbeaglebone();

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

	if (isbb) {
		gpmc_setup(0, GPMCACCESSTIME, GPMC_SIZE_16MB, true, 1);

		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);
		}
	}
}

void bus_setpin(int pin, int value)
{
	if (isbb) {
		gpio_writevalue(gpio_pins[pin], value & 0x1);
	}
}

void bus_writebyte(uint8_t address, uint8_t data)
{
	if (isbb) {
		*(extbus + address) = data;
	}
}

uint8_t bus_readbyte(uint8_t address)
{
	if (isbb) {
		return *(extbus + address);
	} else {
		return 0;
	}
}

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

		}
	}
}