/*
 * Raspberry Pi PIC Programmer using GPIO connector
 * Copyright 2012 Giorgio Vazzana
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

/* Compile: gcc -Wall -O rpp.c -o rpp */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

/* GPIO registers address */
#define BCM2708_PERI_BASE  0x20000000
#define GPIO_BASE          (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
#define BLOCK_SIZE         (256)

/* GPIO setup macros. Always use GPIO_IN(x) before using GPIO_OUT(x) or GPIO_ALT(x,y) */
#define GPIO_IN(g)    *(gpio+((g)/10))   &= ~(7<<(((g)%10)*3))
#define GPIO_OUT(g)   *(gpio+((g)/10))   |=  (1<<(((g)%10)*3))
#define GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))

#define GPIO_SET(g)   *(gpio+7)  = 1<<(g)  /* sets   bit which are 1, ignores bit which are 0 */
#define GPIO_CLR(g)   *(gpio+10) = 1<<(g)  /* clears bit which are 1, ignores bit which are 0 */
#define GPIO_LEV(g)  (*(gpio+13) >> (g)) & 0x00000001

/* GPIO <-> PIC connections */
#define PIC_CLK    4	/* Output */
#define PIC_DATA   7	/* Output */
#define PIC_DATAIN 8	/* Input  */
#define PIC_MCLR   9	/* Output */
#define DELAY      40	/* microseconds */

/* 8K program memory + configuration memory + eeprom data */
#define PICMEMSIZE (0x2100 + 0xFF)

struct picmemory {
	uint16_t  program_memory_used_cells;
	uint16_t  program_memory_max_used_address;

	uint8_t   has_configuration_data;
	uint8_t   has_eeprom_data;

	uint16_t *data;		/* 14-bit data */
	uint8_t  *filled;	/* 1 if this cell is used */
};

struct picmicro {
	uint16_t device_id;
	char     name[16];
	size_t   program_memory_size;
	size_t   data_memory_size;

	int program_cycle_time;		/* in microseconds */
	int eeprom_program_cycle_time;
	int erase_and_program_cycle_time;
	int bulk_erase_cycle_time;

	uint8_t load_configuration_cmd;
	uint8_t load_data_for_program_memory_cmd;
	uint8_t load_data_for_data_memory_cmd;
	uint8_t read_data_from_program_memory_cmd;
	uint8_t read_data_from_data_memory_cmd;
	uint8_t increment_address_cmd;
	uint8_t begin_erase_programming_cycle_cmd;
	uint8_t begin_programming_only_cycle_cmd;
	uint8_t bulk_erase_program_memory_cmd;
	uint8_t bulk_erase_data_memory_cmd;
};

//#define PIC16F84   0xFFFF	/* cannot be autodetected */
#define PIC16F84A  0x0560
#define PIC16F627A 0x1040
#define PIC16F628A 0x1060
#define PIC16F648A 0x1100
#define PIC16F870  0x0D00
#define PIC16F871  0x0D20
#define PIC16F872  0x08E0
#define PIC16F873  0x0960
#define PIC16F874  0x0920
#define PIC16F876  0x09E0
#define PIC16F877  0x09A0

//const struct picmicro pic16f84   = {PIC16F84,   "pic16f84",    0x400,  64, 20000, 20000, 20000, 10000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0xFF, 0x09, 0x0B};
const struct picmicro pic16f84a  = {PIC16F84A,  "pic16f84a",   0x400,  64,  4000,  4000,  8000, 10000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x09, 0x0B};
const struct picmicro pic16f627a = {PIC16F627A, "pic16f627a",  0x400, 128,  4000,  6000,  4000,  6000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0xFF, 0x08, 0x09, 0x0B};
const struct picmicro pic16f628a = {PIC16F628A, "pic16f628a",  0x800, 128,  4000,  6000,  4000,  6000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0xFF, 0x08, 0x09, 0x0B};
const struct picmicro pic16f648a = {PIC16F648A, "pic16f648a", 0x1000, 256,  4000,  6000,  4000,  6000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0xFF, 0x08, 0x09, 0x0B};
const struct picmicro pic16f870  = {PIC16F870,  "pic16f870",   0x800,  64,  4000,  4000,  8000,  8000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x01, 0x07};
const struct picmicro pic16f871  = {PIC16F871,  "pic16f871",   0x800,  64,  4000,  4000,  8000,  8000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x01, 0x07};
const struct picmicro pic16f872  = {PIC16F872,  "pic16f872",   0x800,  64,  4000,  4000,  8000,  8000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x01, 0x07};
const struct picmicro pic16f873  = {PIC16F873,  "pic16f873",  0x1000, 128,  4000,  4000,  8000,  8000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x01, 0x07};
const struct picmicro pic16f874  = {PIC16F874,  "pic16f874",  0x1000, 128,  4000,  4000,  8000,  8000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x01, 0x07};
const struct picmicro pic16f876  = {PIC16F876,  "pic16f876",  0x2000, 256,  4000,  4000,  8000,  8000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x01, 0x07};
const struct picmicro pic16f877  = {PIC16F877,  "pic16f877",  0x2000, 256,  4000,  4000,  8000,  8000, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x18, 0x01, 0x07};

const struct picmicro *piclist[] = {&pic16f84a, &pic16f627a, &pic16f628a, &pic16f648a, &pic16f870, &pic16f871, &pic16f872, &pic16f873, &pic16f874, &pic16f876, &pic16f877, NULL};

int                mem_fd;
void              *gpio_map;
volatile uint32_t *gpio;


/* Set up a memory regions to access GPIO */
void setup_io()
{
	/* open /dev/mem */
	mem_fd = open("/dev/mem", O_RDWR|O_SYNC);
	if (mem_fd == -1) {
		perror("Cannot open /dev/mem");
		exit(1);
	}

	/* mmap GPIO */
	gpio_map = mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, GPIO_BASE);
	if (gpio_map == MAP_FAILED) {
		perror("mmap() failed");
		exit(1);
	}

	/* Always use volatile pointer! */
	gpio = (volatile uint32_t *)gpio_map;

	/* Configure GPIOs */
	GPIO_IN(PIC_CLK); /* must use GPIO_IN before we can use GPIO_OUT */
	GPIO_OUT(PIC_CLK);

	GPIO_IN(PIC_DATA);
	GPIO_OUT(PIC_DATA);

	GPIO_IN(PIC_DATAIN);

	GPIO_IN(PIC_MCLR);
	GPIO_OUT(PIC_MCLR);

	GPIO_CLR(PIC_CLK);
	GPIO_CLR(PIC_DATA);
	GPIO_CLR(PIC_DATAIN);
	GPIO_CLR(PIC_MCLR);
	usleep(DELAY);
}

/* Release GPIO memory region */
void close_io()
{
	int ret;

	/* munmap GPIO */
	ret = munmap(gpio_map, BLOCK_SIZE);
	if (ret == -1) {
		perror("munmap() failed");
		exit(1);
	}

	/* close /dev/mem */
	ret = close(mem_fd);
	if (ret == -1) {
		perror("Cannot close /dev/mem");
		exit(1);
	}
}

void free_picmemory(struct picmemory **ppm)
{
	free((*ppm)->data);
	free((*ppm)->filled);
	free(*ppm);
}

/* Read a file in Intel HEX 16-bit format and return a pointer to the picmemory
   struct on success, or NULL on error */
struct picmemory *read_inhx16(char *infile, int debug)
{
	FILE *fp;
	int linenum;
	char line[256], *ptr;
	size_t linelen;
	int nread;

	uint16_t i;
	uint8_t  start_code;
	uint8_t  byte_count;
	uint16_t address;
	uint8_t  record_type;
	uint16_t data;
	uint8_t  checksum_calculated;
	uint8_t  checksum_read;

	struct picmemory *pm;

	fp = fopen(infile, "r");
	if (fp == NULL) {
		fprintf(stderr, "Error: cannot open source file %s.\n", infile);
		return NULL;
	}

	pm = calloc(1, sizeof(*pm));
	if (pm) {
		pm->data   = calloc(PICMEMSIZE, sizeof(*pm->data));
		pm->filled = calloc(PICMEMSIZE, sizeof(*pm->filled));
	}
	if (!pm || !pm->data || !pm->filled) {
		fprintf(stderr, "Error: calloc() failed.\n");
		return NULL;
	}

	fprintf(stderr, "Reading hex file...\n");

	linenum = 0;
	while (1) {
		ptr = fgets(line, 256, fp);

		if (ptr != NULL) {
			linenum++;
			linelen = strlen(line);
			if (debug) {
				fprintf(stderr, "  line %d (%zd bytes): '", linenum, linelen);
				for (i = 0; i < linelen; i++) {
					if (line[i] == '\n')
						fprintf(stderr, "\\n");
					else if (line[i] == '\r')
						fprintf(stderr, "\\r");
					else
						fprintf(stderr, "%c", line[i]);
				}
			fprintf(stderr, "'\n");
			}

			start_code = line[0];
			if (start_code != ':') {
				fprintf(stderr, "Error: invalid start code.\n");
				free_picmemory(&pm);
				return NULL;
			}

			nread = sscanf(&line[1], "%2hhx", &byte_count);
			if (nread != 1) {
				fprintf(stderr, "Error: cannot read byte count.\n");
				free_picmemory(&pm);
				return NULL;
			}
			if (debug)
				fprintf(stderr, "  byte_count  = 0x%02X\n", byte_count);


			nread = sscanf(&line[3], "%4hx", &address);
			if (nread != 1) {
				fprintf(stderr, "Error: cannot read address.\n");
				free_picmemory(&pm);
				return NULL;
			}
			if (debug)
				fprintf(stderr, "  address     = 0x%04X\n", address);

			nread = sscanf(&line[7], "%2hhx", &record_type);
			if (nread != 1) {
				fprintf(stderr, "Error: cannot read record type.\n");
				free_picmemory(&pm);
				return NULL;
			}
			if (debug)
				fprintf(stderr, "  record_type = 0x%02X (%s)\n", record_type, record_type == 0 ? "data" : (record_type == 1 ? "EOF" : "Unknown"));
			if (record_type != 0 && record_type != 1) {
				fprintf(stderr, "Error: unknown record type.\n");
				free_picmemory(&pm);
				return NULL;
			}

			checksum_calculated  = byte_count;
			checksum_calculated += (address >> 8) & 0xFF;
			checksum_calculated += address & 0xFF;
			checksum_calculated += record_type;

			for (i = 0; i < byte_count; i++) {
				nread = sscanf(&line[9+4*i], "%4hx", &data);
				if (nread != 1) {
					fprintf(stderr, "Error: cannot read data.\n");
					free_picmemory(&pm);
					return NULL;
				}
				if (debug)
					fprintf(stderr, "  data        = 0x%04X\n", data);
				checksum_calculated += (data >> 8) & 0xFF;
				checksum_calculated += data & 0xFF;

				if (address + i < 0x2000) {
					pm->program_memory_used_cells       += 1;
					pm->program_memory_max_used_address  = address + i;
				} else if (0x2000 <= address + i && address + i < 0x2008)
					pm->has_configuration_data = 1;
				else if (address + i >= 0x2100)
					pm->has_eeprom_data = 1;

				pm->data[address + i]   = data;
				pm->filled[address + i] = 1;
			}

			checksum_calculated = (checksum_calculated ^ 0xFF) + 1;

			nread = sscanf(&line[9+4*i], "%2hhx", &checksum_read);
			if (nread != 1) {
				fprintf(stderr, "Error: cannot read checksum.\n");
				free_picmemory(&pm);
				return NULL;
			}
			if (debug)
				fprintf(stderr, "  checksum    = 0x%02X\n", checksum_read);

			if (checksum_calculated != checksum_read) {
				fprintf(stderr, "Error: checksum does not match.\n");
				free_picmemory(&pm);
				return NULL;
			}

			if (debug)
				fprintf(stderr, "\n");

			if (record_type == 1)
				break;
		} else {
			fprintf(stderr, "Error: unexpected EOF.\n");
			free_picmemory(&pm);
			return NULL;
		}
	}

	fclose(fp);

	return pm;
}

/* Write the filled cells in struct picmemory to a Intel HEX 16-bit file */
void write_inhx16(struct picmemory *pm, char *outfile)
{
	FILE *fp;
	uint16_t i, j, k, start, stop;
	uint8_t  byte_count;
	uint16_t address;
	uint8_t  record_type;
	uint16_t data;
	uint8_t  checksum_calculated;

	fp = fopen(outfile, "w");
	if (fp == NULL) {
		fprintf(stderr, "Error: cannot open destination file %s.\n", outfile);
		return;
	}

	fprintf(stderr, "Writing hex file...\n");

	for (i = 0; i < PICMEMSIZE; i += 8) {
		j = 0;
		while (j != 8) {
			for ( ; j < 8; j++) {
				if (pm->filled[i+j])
					break;
			}
			start = j;

			for ( ; j < 8; j++) {
				if (!pm->filled[i+j])
					break;
			}
			stop = j;

			byte_count  = stop - start;
			if (byte_count > 0) {
				address     = i + start;
				record_type = 0x00;
				fprintf(fp, ":%02X%04X%02X", byte_count, address, record_type);

				checksum_calculated  = byte_count;
				checksum_calculated += (address >> 8) & 0xFF;
				checksum_calculated += address & 0xFF;
				checksum_calculated += record_type;

				for (k = start; k < stop; k++) {
					data = pm->data[i+k];
					fprintf(fp, "%04X", data);
					checksum_calculated += (data >> 8) & 0xFF;
					checksum_calculated += data & 0xFF;
				}

				checksum_calculated = (checksum_calculated ^ 0xFF) + 1;
				fprintf(fp, "%02X\n", checksum_calculated);
			}
		}
	}
	fprintf(fp, ":00000001FF\n");
	fclose(fp);
}

/* Send a 6-bit command to the PIC */
void pic_send_cmd(uint8_t cmd)
{
	int i;

	for (i = 0; i < 6; i++) {
		GPIO_SET(PIC_CLK);
		if ((cmd >> i) & 0x01)
			GPIO_SET(PIC_DATA);
		else
			GPIO_CLR(PIC_DATA);
		usleep(DELAY);	/* Setup time */
		GPIO_CLR(PIC_CLK);
		usleep(DELAY);	/* Hold time */
	}
	GPIO_CLR(PIC_DATA);
	usleep(DELAY);
}

/* Read 14-bit data from the PIC (start bit, 14-bit data, stop bit. lsb first) */
uint16_t pic_read_data(void)
{
	int i;
	uint16_t data = 0;

	for (i = 0; i < 16; i++) {
		GPIO_SET(PIC_CLK);
		usleep(DELAY);	/* Wait for data to be valid */
		data |= (GPIO_LEV(PIC_DATAIN)) << i;
		GPIO_CLR(PIC_CLK);
		usleep(DELAY);
	}
	data = (data >> 1) & 0x3FFF;

	return data;
}

/* Load 14-bit data to the PIC (start bit, 14-bit data, stop bit. lsb first) */
void pic_load_data(uint16_t data)
{
	int i;

	/* Insert start and stop bit (0s) */
	data = (data << 1) & 0x7FFE;
	for (i = 0; i < 16; i++) {
		GPIO_SET(PIC_CLK);
		if ((data >> i) & 0x01)
			GPIO_SET(PIC_DATA);
		else
			GPIO_CLR(PIC_DATA);
		usleep(DELAY);	/* Setup time */
		GPIO_CLR(PIC_CLK);
		usleep(DELAY);	/* Hold time */
	}
	GPIO_CLR(PIC_DATA);
	usleep(DELAY);
}

/* Read PIC device id word, located at 0x2006. Used to autodetect the chip */
uint16_t pic_read_device_id_word(const struct picmicro *pic)
{
	int i;
	uint16_t data;

	GPIO_SET(PIC_MCLR);	/* Enter Program/Verify Mode */
	usleep(DELAY);

	pic_send_cmd(pic->load_configuration_cmd);
	pic_load_data(0x0000);
	for (i = 0; i < 6; i++)
		pic_send_cmd(pic->increment_address_cmd);
	pic_send_cmd(pic->read_data_from_program_memory_cmd);
	data = pic_read_data();

	GPIO_CLR(PIC_MCLR);	/* Exit Program/Verify Mode */
	usleep(DELAY);

	return data;
}

/* Bulk erase the chip */
void pic_bulk_erase(const struct picmicro *pic, int debug)
{
	fprintf(stderr, "Bulk erasing chip...\n");

	if (pic->device_id == PIC16F870 || pic->device_id == PIC16F871 ||
	    pic->device_id == PIC16F872 || pic->device_id == PIC16F873 ||
	    pic->device_id == PIC16F874 || pic->device_id == PIC16F876 ||
	    pic->device_id == PIC16F877) {
		if (debug)
			fprintf(stderr, "  Erasing program memory...\n");
		GPIO_SET(PIC_MCLR);
		usleep(DELAY);
		pic_send_cmd(pic->load_data_for_program_memory_cmd);	/* Clear program memory only */
		pic_load_data(0x3FFF);
		pic_send_cmd(pic->bulk_erase_program_memory_cmd);
		pic_send_cmd(pic->bulk_erase_data_memory_cmd);
		pic_send_cmd(pic->begin_erase_programming_cycle_cmd);
		usleep(pic->bulk_erase_cycle_time);
		pic_send_cmd(pic->bulk_erase_program_memory_cmd);
		pic_send_cmd(pic->bulk_erase_data_memory_cmd);
		GPIO_CLR(PIC_MCLR);
		usleep(DELAY);

		if (debug)
			fprintf(stderr, "  Erasing data memory...\n");
		GPIO_SET(PIC_MCLR);
		usleep(DELAY);
		pic_send_cmd(pic->load_data_for_data_memory_cmd);	/* Clear data memory */
		pic_load_data(0x3FFF);
		pic_send_cmd(pic->bulk_erase_program_memory_cmd);
		pic_send_cmd(pic->bulk_erase_data_memory_cmd);
		pic_send_cmd(pic->begin_erase_programming_cycle_cmd);
		usleep(pic->bulk_erase_cycle_time);
		pic_send_cmd(pic->bulk_erase_program_memory_cmd);
		pic_send_cmd(pic->bulk_erase_data_memory_cmd);
		GPIO_CLR(PIC_MCLR);
		usleep(DELAY);

		return;
	}

	if (debug)
		fprintf(stderr, "  Erasing program and configuration memory...\n");
	GPIO_SET(PIC_MCLR);
	usleep(DELAY);
	pic_send_cmd(pic->load_configuration_cmd);	/* Clear both program and configuration memory */
	pic_load_data(0x3FFF);
#if 0
	pic_send_cmd(pic->load_data_for_program_memory_cmd); /* Clear program memory only */
	pic_load_data(0x3FFF);
#endif
	pic_send_cmd(pic->bulk_erase_program_memory_cmd);
	pic_send_cmd(pic->begin_programming_only_cycle_cmd);
	usleep(pic->bulk_erase_cycle_time);
	GPIO_CLR(PIC_MCLR);
	usleep(DELAY);

	if (debug)
		fprintf(stderr, "  Erasing data memory...\n");
	GPIO_SET(PIC_MCLR);
	usleep(DELAY);
	pic_send_cmd(pic->load_data_for_data_memory_cmd);	/* Clear data memory */
	pic_load_data(0x3FFF);
	pic_send_cmd(pic->bulk_erase_data_memory_cmd);
	pic_send_cmd(pic->begin_programming_only_cycle_cmd);
	usleep(pic->bulk_erase_cycle_time);
	GPIO_CLR(PIC_MCLR);
	usleep(DELAY);
}

/* Read program memory, configuration memory and data memory of the PIC
   and write the contents to a .hex file */
void pic_read(const struct picmicro *pic, char *outfile, int skipones, int debug)
{
	struct picmemory *pm;
	uint16_t addr, data;

	pm = calloc(1, sizeof(*pm));
	if (pm) {
		pm->data   = calloc(PICMEMSIZE, sizeof(*pm->data));
		pm->filled = calloc(PICMEMSIZE, sizeof(*pm->filled));
	}
	if (!pm || !pm->data || !pm->filled) {
		fprintf(stderr, "Error: calloc() failed.\n");
		return;
	}

	fprintf(stderr, "Reading chip...\n");

	GPIO_SET(PIC_MCLR);
	usleep(DELAY);
	/* Read Program Memory */
	if (debug)
		fprintf(stderr, "  Program memory:\n");
	for (addr = 0; addr < pic->program_memory_size; addr++) {
		pic_send_cmd(pic->read_data_from_program_memory_cmd);
		data = pic_read_data();
		if (debug)
			fprintf(stderr, "  addr = 0x%04X  data = 0x%04X\n", addr, data);

		if (!skipones || data != 0x3FFF) {
			pm->program_memory_used_cells       += 1;
			pm->program_memory_max_used_address  = addr;
			pm->data[addr]                       = data;
			pm->filled[addr]                     = 1;
		}
		pic_send_cmd(pic->increment_address_cmd);
	}

	/* Read Configuration Memory */
	if (debug)
		fprintf(stderr, "  Configuration memory:\n");
	pic_send_cmd(pic->load_configuration_cmd);
	pic_load_data(0x0000);
	for (addr = 0x2000; addr < 0x2008; addr++) {
		if (addr <= 0x2003 /*|| addr == 0x2006*/ || addr == 0x2007) {
			pic_send_cmd(pic->read_data_from_program_memory_cmd);
			data = pic_read_data();
			if (debug)
				fprintf(stderr, "  addr = 0x%04X  data = 0x%04X\n", addr, data);

			if (!skipones || (addr <= 0x2003 && data != 0x3FFF) || addr == 0x2007) {
				pm->has_configuration_data = 1;
				pm->data[addr]             = data;
				pm->filled[addr]           = 1;
			}
		}
		pic_send_cmd(pic->increment_address_cmd);
	}

	/* Read Data Memory    (but first reset PC, at most only the 8 least
	   significant bits of PC are decoded when reading data memory) */
	if (debug)
		fprintf(stderr, "  Data memory:\n");
	pic_send_cmd(pic->load_configuration_cmd);
	pic_load_data(0x0000);
	for (addr = 0x2100; addr < 0x2100 + pic->data_memory_size; addr++) {
		pic_send_cmd(pic->read_data_from_data_memory_cmd);
		data = pic_read_data();
		if (debug)
			fprintf(stderr, "  addr = 0x%04X  data = 0x%04X\n", addr, data);

		data &= 0x00FF;
		if (!skipones || data != 0x00FF) {
			pm->has_eeprom_data = 1;
			pm->data[addr]      = data;
			pm->filled[addr]    = 1;
		}
		pic_send_cmd(pic->increment_address_cmd);
	}
	GPIO_CLR(PIC_MCLR);
	usleep(DELAY);

	write_inhx16(pm, outfile);
	free_picmemory(&pm);
}

/* Bulk erase the chip, and then write contents of the .hex file to the PIC */
void pic_write(const struct picmicro *pic, char *infile, int debug)
{
	int error = 0;
	uint16_t addr, data;
	struct picmemory *pm;

	pm = read_inhx16(infile, debug);
	if (!pm)
		return;

	/* Bulk erase the chip first */
	pic_bulk_erase(pic, debug);

	fprintf(stderr, "Writing chip...\n");

	/* Write Program Memory */
	if (debug)
		fprintf(stderr, "  Program memory:\n");
	GPIO_SET(PIC_MCLR);
	usleep(DELAY);
	for (addr = 0; addr <= pm->program_memory_max_used_address; addr++) {
		if (pm->filled[addr]) {
			pic_send_cmd(pic->load_data_for_program_memory_cmd);
			pic_load_data(pm->data[addr]);
			pic_send_cmd(pic->begin_programming_only_cycle_cmd);
			usleep(pic->program_cycle_time);

			pic_send_cmd(pic->read_data_from_program_memory_cmd);
			data = pic_read_data();

			if (debug)
				fprintf(stderr, "  addr = 0x%04X  written = 0x%04X  read = 0x%04X\n", addr, pm->data[addr], data);

			if (pm->data[addr] != data) {
				fprintf(stderr, "Error: addr = 0x%04X, written = 0x%04X, read = 0x%04X\n", addr, pm->data[addr], data);
				error = 1;
				break;
			}
		}
		pic_send_cmd(pic->increment_address_cmd);
	}
	GPIO_CLR(PIC_MCLR);
	usleep(DELAY);

	if (error)
		return;

	/* Write Configuration Memory */
	if (pm->has_configuration_data) {
		if (debug)
			fprintf(stderr, "  Configuration memory:\n");
		GPIO_SET(PIC_MCLR);
		usleep(DELAY);
		pic_send_cmd(pic->load_configuration_cmd);
		pic_load_data(pm->data[0x2000]);
		for (addr = 0x2000; addr < 0x2008; addr++) {
			if ((addr <= 0x2003 || addr == 0x2007) && pm->filled[addr]) {
				pic_send_cmd(pic->load_data_for_program_memory_cmd);
				pic_load_data(pm->data[addr]);

				if ((pic->device_id == PIC16F84A && addr == 0x2007) ||
				    pic->device_id == PIC16F870 || pic->device_id == PIC16F871 ||
				    pic->device_id == PIC16F872 || pic->device_id == PIC16F873 ||
				    pic->device_id == PIC16F874 || pic->device_id == PIC16F876 ||
				    pic->device_id == PIC16F877) {
					pic_send_cmd(pic->begin_erase_programming_cycle_cmd);
					usleep(pic->erase_and_program_cycle_time);
				} else {
					pic_send_cmd(pic->begin_programming_only_cycle_cmd);
					usleep(pic->program_cycle_time);
				}

				pic_send_cmd(pic->read_data_from_program_memory_cmd);
				data = pic_read_data();

				if (debug)
					fprintf(stderr, "  addr = 0x%04X  written = 0x%04X  read = 0x%04X\n", addr, pm->data[addr], data);

				if (pm->data[addr] != data) {
					fprintf(stderr, "Error: addr = 0x%04X, written = 0x%04X, read = 0x%04X\n", addr, pm->data[addr], data);
					error = 1;
					break;
				}
			}
			pic_send_cmd(pic->increment_address_cmd);
		}
		GPIO_CLR(PIC_MCLR);
		usleep(DELAY);
	}
	if (error)
		return;

	/* Write Data Memory */
	if (pm->has_eeprom_data) {
		if (debug)
			fprintf(stderr, "  Data memory:\n");
		GPIO_SET(PIC_MCLR);
		usleep(DELAY);
		for (addr = 0x2100; addr < 0x2100 + pic->data_memory_size; addr++) {
			if (pm->filled[addr]) {
				pic_send_cmd(pic->load_data_for_data_memory_cmd);
				pic_load_data(pm->data[addr]);
				pic_send_cmd(pic->begin_programming_only_cycle_cmd);
				usleep(pic->eeprom_program_cycle_time);

				pic_send_cmd(pic->read_data_from_data_memory_cmd);
				data = pic_read_data();

				if (debug)
					fprintf(stderr, "  addr = 0x%04X  written = 0x%04X  read = 0x%04X\n", addr, pm->data[addr], data);

				if (pm->data[addr] != (data & 0x00FF)) {
					fprintf(stderr, "Error: addr = 0x%04X, written = 0x%04X, read = 0x%04X\n", addr, pm->data[addr], data);
					error = 1;
					break;
				}
			}
			pic_send_cmd(pic->increment_address_cmd);
		}
		GPIO_CLR(PIC_MCLR);
		usleep(DELAY);
	}
}

void usage(void)
{
	const struct picmicro **ppic;
	uint8_t comma = 0;

	fprintf(stderr,
"Usage: rpp [options]\n"
"       -h          print help\n"
"       -D          turn debug on\n"
"       -i file     input file\n"
"       -o file     output file (ofile.hex)\n"
"       -r          read chip\n"
"       -w          bulk erase and write chip\n"
"       -e          bulk erase chip\n"
"       -s          skip all-ones memory locations\n"
"\n"
"Supported PICs:"
	);
	for (ppic = piclist; *ppic; ppic++) {
		fprintf(stderr, "%s %s", comma ? "," : "", (*ppic)->name);
		comma = 1;
	}
	fprintf(stderr, "\n");
}

int main(int argc, char *argv[])
{
	int opt, debug = 0, skipones = 0, function = 0;
	char *infile  = NULL;
	char *outfile = "ofile.hex";
	uint16_t device_id;
	const struct picmicro *pic, **ppic;

	fprintf(stderr, "Raspberry Pi PIC Programmer, v0.1\n\n");

	while ((opt = getopt(argc, argv, "hDi:o:rwes")) != -1) {
		switch (opt) {
		case 'h':
			usage();
			exit(0);
			break;
		case 'D':
			debug = 1;
			break;
		case 'i':
			infile = optarg;
			break;
		case 'o':
			outfile = optarg;
			break;
		case 'r':
			function |= 0x01;
			break;
		case 'w':
			function |= 0x02;
			break;
		case 'e':
			function |= 0x04;
			break;
		case 's':
			skipones = 1;
			break;
		default:
			fprintf(stderr, "\n");
			usage();
			exit(1);
		}
	}
#if 0
	fprintf(stderr, "debug = %d, infile = %s, outfile = %s, function = 0x%02x, skipones = %d\n",
	        debug, infile == NULL ? "NULL" : infile, outfile, function, skipones);
#endif

	if (function == 0x02 && !infile) {
		fprintf(stderr, "Please specify an input file with -i option.\n");
		exit(1);
	}

	/* Setup gpio pointer for direct register access */
	setup_io();

	/* Read PIC device id word */
	pic = &pic16f84a;
	device_id = pic_read_device_id_word(pic);
	fprintf(stderr, "device_id = 0x%04x\n", device_id);
	for (ppic = piclist; *ppic; ppic++) {
		if ((*ppic)->device_id == (device_id & 0xFFE0)) {
			pic = *ppic;
			break;
		}
	}
	if (*ppic == NULL) {
		fprintf(stderr, "Error: unknown device or programmer not connected.\n");
		exit(1);
	} else
		fprintf(stderr, "%s detected, revision 0x%02x\n", pic->name, device_id & 0x001F);

	switch (function) {
	case 0x00:
		/* no function selected, exit */
		break;
	case 0x01:
		pic_read(pic, outfile, skipones, debug);
		break;
	case 0x02:
		pic_write(pic, infile, debug);
		break;
	case 0x04:
		pic_bulk_erase(pic, debug);
		break;
	default:
		fprintf(stderr, "\nPlease select only one option in -r, -w, -e.\n");
	};

	close_io();

	return 0;
}