/*
 * picpen.c - PIC (18F{2,4}xJxx) Programmer/Emulator for Nanonote
 *
 * Written 2012 by Werner Almesberger
 * Copyright 2012 Werner Almesberger
 *
 * 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 2 of the License, or
 * (at your option) any later version.
 */


#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

#include "gpio-xburst.h"


#define	POWER_OFF	3, 2	/* PD02 */
#define	nMCLR		3, 13	/* PD13 DAT3 */
#define	PGD		3, 8	/* PD09 CMD */
#define	PGC		3, 9	/* PD08 CLK */

#define	ICSP_KEY	0x4d434850

#define	P5_US	1	/* Delay Between 4-Bit Command and Command Operand */
#define	P5A_US	1	/* Delay Between 4-Bit Command Operand and Next 4-Bit
			   Command */
#define	P6_US	1	/* Delay Between Last PGC down of Command Byte
			   to First PGC up of Read of Data Word */
#define	P9_US	1200	/* Delay to allow Block Programming to Occur */
#define	P10_US	54000	/* Delay to allow Row Erase to Occur */
#define	P11_US	524000	/* Delay to allow Bulk Erase to Occur */
#define	P12_US	400	/* Input Data Hold Time from nMCLR up */
#define	P13_US	1	/* VDD up Setup Time to nMCLR up */
#define	P16_US	1	/* Delay Between Last PGC down and nMCLR down */
#define	P17_US	3	/* nMCLR down to VDD down */
#define	P19_US	4000	/* Delay from First nMCLR down to First PGC up for
			   Key Sequence on PGD */
#define	P20_US	1	/* Delay from Last PGC down for Key Sequence on
			   PGD to Second nMCLR up */

#define	CMD_INSN	0
#define	CMD_TABLAT	0x2
#define	CMD_READ_INC	0x9	/* post-increment by 1 */
#define	CMD_WRITE	0xc
#define	CMD_WRITE_INC	0xd	/* post-increment by 2 */
#define	CMD_WRITE_START	0xf

#define	INSN_MOVLW	0x0e
#define	INSN_MOVWF	0x6e
#define	INSN_MOVF_W_0	0x50
#define	INSN_MOVFF1	0xcf
#define	INSN_MOVFF2	0xff

#define	REG_TABLAT	0xf5
#define	REG_TBLPTRL	0xf6
#define	REG_TBLPTRH	0xf7
#define	REG_TBLPTRU	0xf8

#define	REG_ANCON0	0x48	/* banked ! */
#define	REG_ANCON1	0x49	/* banked ! */
#define	REG_PORTA	0x80
#define	REG_PORTC	0x82
#define	REG_LATA	0x89
#define	REG_TRISA	0x92	/* 1 = in */
#define	REG_EECON1	0xa6
#define	REG_CTMUICON	0xb1
#define	REG_CTMUCONL	0xb2
#define	REG_CTMUCONH	0xb3
#define	REG_ADCON1	0xc1
#define	REG_ADCON0	0xc2
#define	REG_ADRESL	0xc3
#define	REG_ADRESH	0xc4

#define	BLOCK		64


struct rec {
	uint32_t addr;
	uint8_t *data;
	int len;
	struct rec *next;
};

static int kb;


/* The multiplier (100) is a wild guess. Tested down to 0. */

static inline void delay(int us)
{
	uint32_t i;

	if (us < 10)
		for (i = 0; i != us*100; i++)
			asm("");
	else
		usleep(us);
}


static void icsp_begin(int power)
{
	int i;

	gpio_init();

	gpio_high(nMCLR);
	gpio_high(PGD);
	gpio_high(PGC);

	if (power) {
		gpio_output(POWER_OFF);
		gpio_output(PGD);
		gpio_output(PGC);
		gpio_output(nMCLR);

		delay(100*1000);	/* precharge */
	}

	gpio_low(PGD);
	gpio_low(PGC);
	gpio_low(nMCLR);

	if (power)
		gpio_low(POWER_OFF);
	delay(P13_US);

	gpio_high(nMCLR);
	delay(1);
	gpio_low(nMCLR);
	delay(P19_US);

	for (i = 31; i >= 0; i--) {
		if ((ICSP_KEY >> i) & 1)
			gpio_high(PGD);
		else
			gpio_low(PGD);
		gpio_high(PGC);
		gpio_low(PGC);
	}

	delay(P20_US);
	gpio_high(nMCLR);
	delay(P12_US);
}


static void icsp_end(void)
{
	gpio_low(PGD);
	gpio_low(PGC);

	delay(P16_US);
	gpio_low(nMCLR);
	delay(P17_US);

	gpio_high(nMCLR);
//	gpio_high(POWER_OFF);

gpio_input(PGD);
gpio_input(PGC);
}


static void icsp_send(uint8_t v, int n)
{
	int i;

	for (i = 0; i != n; i++) {
		if ((v >> i) & 1)
			gpio_high(PGD);
		else
			gpio_low(PGD);
		gpio_high(PGC);
		gpio_low(PGC);
	}
}


static uint8_t icsp_recv(int n)
{
	uint8_t v = 0;
	int i;

	for (i = 0; i != n; i++) {
		gpio_high(PGC);
		gpio_low(PGC);
		if (gpio_get(PGD))
			v |= 1 << i;
	}
	return v;
}


static void icsp_write(uint8_t cmd, uint8_t a, uint8_t b)
{
	icsp_send(cmd, 4);
	delay(P5_US);
	icsp_send(b, 8);
	icsp_send(a, 8);
	delay(P5A_US);
}


static void write_reg(uint8_t addr, uint8_t v)
{
	icsp_write(CMD_INSN, INSN_MOVLW, v);
	icsp_write(CMD_INSN, INSN_MOVWF, addr);
}


static uint8_t read_reg(uint8_t addr)
{
	uint8_t v;

	icsp_write(CMD_INSN, 0x50, addr);
	/* for some strange reason we need two writes */
	icsp_write(CMD_INSN, INSN_MOVWF, REG_TABLAT);
	icsp_write(CMD_INSN, INSN_MOVWF, REG_TABLAT);

	icsp_send(CMD_TABLAT, 4);
	delay(P5_US);

	icsp_send(0, 8);
	delay(P6_US);
	gpio_input(PGD);

	v = icsp_recv(8);
	delay(P5A_US);
	gpio_output(PGD);

	return v;
}


static void set_tblptr(uint32_t addr)
{
	write_reg(REG_TBLPTRU, addr >> 16);
	write_reg(REG_TBLPTRH, addr >> 8);
	write_reg(REG_TBLPTRL, addr);
}


static void read_mem(uint32_t addr, uint8_t *buf, int len)
{
	set_tblptr(addr);

	while (len--) {
		icsp_send(CMD_READ_INC, 4);
		delay(P5_US);
		icsp_send(0, 8);
		delay(P6_US);
		gpio_input(PGD);

		*buf++ = icsp_recv(8);
		delay(P5A_US);
		gpio_output(PGD);
	}
}


static void bulk_erase(void)
{
	set_tblptr(0x3c0005);
	icsp_write(CMD_WRITE, 0x01, 0x01);
	set_tblptr(0x3c0004);
	icsp_write(CMD_WRITE, 0x80, 0x80);
	icsp_write(CMD_INSN, 0, 0);

	icsp_send(0, 4);
	delay(P11_US+P10_US);

	icsp_send(0, 16);
	delay(P5A_US);	/* guess*/
}


static void write_mem(uint32_t addr, const uint8_t *buf, int len)
{
	int i;
	uint8_t first;

	assert(!(addr & (BLOCK-1)));
	assert(!(len & (BLOCK-1)));

	/* BSF EECON1, WREN */
	icsp_write(CMD_INSN, 0x84, REG_EECON1);

	while (len) {
		set_tblptr(addr);
		for (i = 0; i != BLOCK/2-1; i++) {
			first = *buf++;
			icsp_write(CMD_WRITE_INC, *buf++, first);
		}
		first = *buf++;
		icsp_write(CMD_WRITE_START, *buf++, first);

		icsp_send(0, 3);
		gpio_low(PGD);
		gpio_high(PGC);
		delay(P9_US);
		gpio_low(PGC);
		delay(P5_US);

		icsp_send(0, 16);
		delay(P5A_US);	/* guess*/

		addr += BLOCK;
		len -= BLOCK;
	}
}


/* ----- Flash-level operations -------------------------------------------- */


static void flash_record(const struct rec *rec)
{
	uint8_t *tmp;
	int off, padded;

	off = rec->addr & (BLOCK-1);
	padded = (rec->len+off+BLOCK-1) & ~(BLOCK-1);
	if (!off && padded == rec->len) {
		write_mem(rec->addr, rec->data, rec->len);
		return;
	}
	tmp = malloc(padded);
	if (!tmp) {
		perror("malloc");
		exit(1);
	}
	memset(tmp, 0xff, off);
	memcpy(tmp+off, rec->data, rec->len);
	memset(tmp+off+rec->len, 0xff, padded-rec->len-off);
	write_mem(rec->addr-off, tmp, padded);
}


static void verify_record(const struct rec *rec)
{
	uint8_t *tmp;
	int i;

	tmp = malloc(rec->len);
	if (!tmp) {
		perror("malloc");
		exit(1);
	}
	read_mem(rec->addr, tmp, rec->len);
	for (i = 0; i != rec->len; i++)
		if (rec->data[i] != tmp[i]) {
			fprintf(stderr,
			    "%04x: wrote %02x != read %02x\n",
			    rec->addr+i, rec->data[i], tmp[i]);
		}
}


static void flash_file(const struct rec *recs)
{
	const struct rec *rec;

	for (rec = recs; rec; rec = rec->next)
		if (rec->addr+rec->len > kb*1024) {
			fprintf(stderr,
			    "record 0x%x+0x%x ends outside Flash of %d bytes\n",
			    rec->addr, rec->len, kb*1024-8);
			exit(1);
		}

	bulk_erase();
	for (rec = recs; rec; rec = rec->next)
		flash_record(rec);

	for (rec = recs; rec; rec = rec->next)
		verify_record(rec);
}


static void dump_flash(void)
{
	uint8_t *tmp, c;
	int i, j;

	tmp = malloc(kb*1024);
	if (!tmp) {
		perror("malloc");
		exit(1);
	}

	read_mem(0, tmp, kb*1024);

	for (i = 0; i != kb*1024; i += 16) {
		printf("%04X: ", i);
		for (j = 0; j != 16; j++)
			printf("%02X ", tmp[i+j]);
		for (j = 0; j != 16; j++) {
			c = tmp[i+j];
			printf("%c", c >= ' ' && c <= '~' ? c : '.');
		}
		printf("\n");
	}
}


/* ----- Experiments ------------------------------------------------------- */


#if 0

static void blink(void)
{
	write_reg(REG_TRISA, 0xfc);
	while (1) {
		write_reg(REG_LATA, 1);
gpio_high(PGD);
		delay(200*1000);
		write_reg(REG_LATA, 2);
gpio_high(PGD);
		delay(200*1000);
	}
}


static void adc(void)
{
int i;

#if 0
for (i = 0; i != 256; i++) {
//	write_reg(REG_ANCON0, 0xff);
//	write_reg(REG_TABLAT, i);
//	write_reg(REG_TABLAT, i);
	printf("%02x ", read_reg(REG_PORTC));
fflush(stdout);
}
#endif
	write_reg(REG_TRISA, 0xfc);
//	write_reg(REG_ANCON0, 0x10);	/* AN4 is analog */
	write_reg(REG_ADCON0, 0x20);	/* AN4, Vss to AVdd */
	write_reg(REG_ADCON1, 0x80);	/* Tad = 0, Fosc/2, right just. */
	write_reg(REG_ADCON0, 0x21);	/* Enable ADC module */
	while (1) {
		uint8_t hi, lo;

		write_reg(REG_ADCON0, 0x23);	/* GO */
		while (read_reg(REG_ADCON0) & 2)
			write(2, ".", 1);
		hi = read_reg(REG_ADRESH);
		lo = read_reg(REG_ADRESL);
		printf("\r%02x %02x (%u)\n", hi, lo, hi << 8 | lo);
	}
	
#if 0
	while (1) {
		write_reg(
	}
#endif
	
}


static void cap(void)
{
	write_reg(REG_CTMUCONH, 0x00);	/* page 405 */
	write_reg(REG_CTMUCONL, 0x90);
	write_reg(REG_CTMUICON, 0x01);

	write_reg(REG_TRISA, 0xfc);
	write_reg(REG_ADCON0, 0x20);	/* AN4, Vss to AVdd */
//	write_reg(REG_ADCON1, 0x8e);	/* Tad = 2, Fosc/32, right just. */
	write_reg(REG_ADCON1, 0x88);	/* Tad = 2, Fosc/2, right just. */
	write_reg(REG_ADCON0, 0x21);	/* Enable ADC module */

	/* bandgap ?!? */

	while (1) {
		uint8_t hi, lo;

		write_reg(REG_CTMUCONH, 0x80);	/* enable CTMU */
		write_reg(REG_CTMUCONL, 0x90);	/* clear EDGxSTAT */
		write_reg(REG_CTMUCONH, 0x82);	/* ground output */
		delay(1);
		write_reg(REG_CTMUCONH, 0x80);	/* end drain */
		write_reg(REG_CTMUCONL, 0x91);	/* current on */
		write_reg(REG_CTMUCONL, 0x90);	/* current off */

		write_reg(REG_ADCON0, 0x23);	/* GO */
		while (read_reg(REG_ADCON0) & 2)
			write(2, ".", 1);
		hi = read_reg(REG_ADRESH);
		lo = read_reg(REG_ADRESL);
		printf("\r%02x %02x (%u)\n", hi, lo, hi << 8 | lo);
	}
}


static void dump(uint32_t addr)
{
	uint8_t buf[BLOCK];
	int i;

	read_mem(addr, buf, BLOCK);
	for (i = 0; i != BLOCK; i++) {
		if (!(i & 15))
			printf("%04X:", i);
		printf(" %02X", buf[i]);
		if ((i & 15) == 15)
			printf("\n");
	}
}


static void rerwr(void)
{
	uint8_t buf[BLOCK];
	int i;

	dump(0x3fffc0);
	dump(0);
	bulk_erase();
	dump(0);
	for (i = 0; i != BLOCK; i++)
		buf[i] = i;
	write_mem(0, buf, BLOCK);
	dump(0x3fffc0);
	dump(0);
}

#endif

/* ----- Chip identification ----------------------------------------------0 */


struct chip {
	const char *name;
	uint8_t id2, id1;
	int kb;
} chips[] = {
	{ "PIC18F24J50",	0x4c, 0x00,  16 },
	{ NULL }
};


#define	DEVID1	0x3ffffe


static void identify(void)
{
	uint8_t id[2];
	const struct chip *chip;

	read_mem(DEVID1, id, 2);
	for (chip = chips; chip->name; chip++) {
		if ((chip->id1 ^ id[0]) & 0xe0)
			continue;
		if (chip->id2 == id[1])
			break;
	}
	fprintf(stderr, "ID = 0x%02x%02x (%s)\n", id[1], id[0],
	    chip->name ? chip->name : "?");
	if (!chip->name)
		exit(1);
	fprintf(stderr, "%d kB Flash\n", chip->kb);
	kb = chip->kb;
}


/* ----- Intel HEX file reader --------------------------------------------- */


static int hex(char c, int lineno)
{
	if (c >= '0' && c <= '9')
		return c-'0';
	if (c >= 'A' && c <= 'F')
		return c-'A'+10;
	fprintf(stderr, "non-hex character \"%c\" in line %d\n", c, lineno);
	exit(1);
}


/* http://en.wikipedia.org/wiki/Intel_HEX */


static struct rec *load_file(const char *name)
{
	struct rec *recs = NULL, *last = NULL, *rec;
	const struct rec *other;
	FILE *file;
	int lineno = 1;
	char line[1000];
	const char *s;
	uint8_t buf[sizeof(line)/2];
	uint8_t *p, *t, sum;
	uint32_t xaddr = 0, addr;

	file = fopen(name, "r");
	if (!file) {
		perror(name);
		exit(1);
	}
	while (fgets(line, sizeof(line), file)) {
		if (*line != ':') {
			fprintf(stderr, "line %d doesn't start with colon\n",
			    lineno);
			exit(1);
		}
		p = buf;
		for (s = line+1; *s > ' '; s += 2)
			*p++ = hex(s[0], lineno) << 4 | hex(s[1], lineno);
		if (p-buf < 5) {
			fprintf(stderr, "short record in line %d\n", lineno);
			exit(1);
		}
		lineno++;
		sum = 0;
		for (t = buf; t != p-1; t++)
			sum += *t;
		if (0x100-sum != p[-1]) {
			fprintf(stderr,
			    "checksum error (0x%02x vs. 0x%02x) in line %d\n",
			    p[-1], 0x100-sum, lineno);
			exit(1);
		}
		switch (buf[3]) {
		case 0:	/* Data record */
			if (p-buf !=  buf[0]+5) {
				fprintf(stderr,
				    "data record of %d bytes has length %d\n",
				    p-buf, buf[0]);
				exit(1);
			}
			addr = xaddr << 16 | buf[1] << 8 | buf[2];
			if (last && last->addr+last->len == addr) {
				last->data = realloc(last->data,
				    last->len+buf[0]);
				if (!last->data) {
					perror("realloc");
					exit(1);
				}
				memcpy(last->data+last->len, buf+4, buf[0]);
				last->len += buf[0];
				break;
			}
			rec = malloc(sizeof(struct rec));
			rec->addr = xaddr << 16 | buf[1] << 8 | buf[2];
			rec->len = buf[0];
			rec->data = malloc(rec->len);
			if (!rec->data) {
				perror("realloc");
				exit(1);
			}
			rec->next = NULL;
			memcpy(rec->data, buf+4, rec->len);
			if (last)
				last->next = rec;
			else
				recs = rec;
			last = rec;
			break;
		case 1:	/* End Of File record */
			goto end;
		case 4:	/* Extended Linear Address record */
			xaddr = buf[4] << 8 | buf[5];
			break;
		default:
			fprintf(stderr,
			    "unrecognized record type 0x%02x in line %d\n",
			    buf[3], lineno);
			exit(1);
		}
	}
end:
	for (rec = recs; rec; rec = rec->next)
		for (other = rec->next; other; other = other->next)
			if (rec->addr < other->addr+other->len &&
			    rec->addr+rec->len > other->addr) {
				fprintf(stderr,
				    "overlapping address ranges 0x%x+0x%x "
				    "and 0x%x+0x%x\n",
				    rec->addr, rec->len, other->addr,
				    other->len);
			}
	fclose(file);
	return recs;
}


/* ----- Command-line parsing ---------------------------------------------- */


static void usage(const char *name)
{
        fprintf(stderr,
"usage: %s [-n] [file.bin]\n", name);
        exit(1);
}


int main(int argc, char **argv)
{
	const struct rec *recs = NULL;
	int power = 1;
	int c;

	while ((c = getopt(argc, argv, "n")) != EOF)
		switch (c) {
		case 'n':
			power = 0;
			break;
		default:
			usage(*argv);
		}

	switch (argc-optind) {
	case 0:
		break;
	case 1:
		recs = load_file(argv[optind]);
		break;
	default:
		usage(*argv);
	}

	icsp_begin(power);
	identify();

	if (recs)
		flash_file(recs);
	else
		dump_flash();
	icsp_end();

	return 0;
}
