diff options
Diffstat (limited to 'pickit2_spi.c')
| -rw-r--r-- | pickit2_spi.c | 505 | 
1 files changed, 505 insertions, 0 deletions
diff --git a/pickit2_spi.c b/pickit2_spi.c new file mode 100644 index 00000000..fb03b024 --- /dev/null +++ b/pickit2_spi.c @@ -0,0 +1,505 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Carl-Daniel Hailfinger + * Copyright (C) 2014 Justin Chevrier + * + * 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; version 2 of the License. + * + * 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 St, Fifth Floor, Boston, MA  02110-1301 USA + */ + +/* + * Connections are as follows: + * + *      +------+-----+----------+ + *      | SPI  | Pin | PICkit2  | + *      +------+-----+----------+ + *      | /CS  | 1   | VPP/MCLR | + *      | VCC  | 2   | VDD      | + *      | GND  | 3   | GND      | + *      | MISO | 4   | PGD      | + *      | SCLK | 5   | PDC      | + *      | MOSI | 6   | AUX      | + *      +------+-----+----------+ + * + * Inspiration and some specifics of the interface came via the AVRDude + * PICkit2 code: https://github.com/steve-m/avrdude/blob/master/pickit2.c + */ + +#include "platform.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <errno.h> + +#if IS_WINDOWS +#include <lusb0_usb.h> +#else +#include <usb.h> +#endif + +#include "flash.h" +#include "chipdrivers.h" +#include "programmer.h" +#include "spi.h" + +static usb_dev_handle *pickit2_handle; + +/* Default USB transaction timeout in ms */ +#define DFLT_TIMEOUT            10000 + +#define CMD_LENGTH              64 +#define ENDPOINT_OUT            0x01 +#define ENDPOINT_IN             0x81 + +#define PICKIT2_VID             0x04D8 +#define PICKIT2_PID             0x0033 + +#define CMD_GET_VERSION         0x76 +#define CMD_SET_VDD             0xA0 +#define CMD_SET_VPP             0xA1 +#define CMD_READ_VDD_VPP        0xA3 +#define CMD_EXEC_SCRIPT         0xA6 +#define CMD_CLR_DLOAD_BUFF      0xA7 +#define CMD_DOWNLOAD_DATA       0xA8 +#define CMD_CLR_ULOAD_BUFF      0xA9 +#define CMD_UPLOAD_DATA         0xAA +#define CMD_END_OF_BUFFER       0xAD + +#define SCR_SPI_READ_BUF        0xC5 +#define SCR_SPI_WRITE_BUF       0xC6 +#define SCR_SET_AUX             0xCF +#define SCR_LOOP                0xE9 +#define SCR_SET_ICSP_CLK_PERIOD 0xEA +#define SCR_SET_PINS            0xF3 +#define SCR_BUSY_LED_OFF        0xF4 +#define SCR_BUSY_LED_ON         0xF5 +#define SCR_MCLR_GND_OFF        0xF6 +#define SCR_MCLR_GND_ON         0xF7 +#define SCR_VPP_PWM_OFF         0xF8 +#define SCR_VPP_PWM_ON          0xF9 +#define SCR_VPP_OFF             0xFA +#define SCR_VPP_ON              0xFB +#define SCR_VDD_OFF             0xFE +#define SCR_VDD_ON              0xFF + +/* Copied from dediprog.c */ +/* Might be useful for other USB devices as well. static for now. */ +/* device parameter allows user to specify one device of multiple installed */ +static struct usb_device *get_device_by_vid_pid(uint16_t vid, uint16_t pid, unsigned int device) +{ +	struct usb_bus *bus; +	struct usb_device *dev; + +	for (bus = usb_get_busses(); bus; bus = bus->next) +		for (dev = bus->devices; dev; dev = dev->next) +			if ((dev->descriptor.idVendor == vid) && +			    (dev->descriptor.idProduct == pid)) { +				if (device == 0) +					return dev; +				device--; +			} + +	return NULL; +} + +static int pickit2_get_firmware_version(void) +{ +	int ret; +	uint8_t command[CMD_LENGTH] = {CMD_GET_VERSION, CMD_END_OF_BUFFER}; + +	ret = usb_interrupt_write(pickit2_handle, ENDPOINT_OUT, (char *)command, CMD_LENGTH, DFLT_TIMEOUT); +	ret = usb_interrupt_read(pickit2_handle, ENDPOINT_IN, (char *)command, CMD_LENGTH, DFLT_TIMEOUT); +	 +	msg_pdbg("PICkit2 Firmware Version: %d.%d\n", (int)command[0], (int)command[1]); +	if (ret != CMD_LENGTH) { +		msg_perr("Command Get Firmware Version failed (%s)!\n", usb_strerror()); +		return 1; +	} + +	return 0; +} + +static int pickit2_set_spi_voltage(int millivolt) +{ +	double voltage_selector; +	switch (millivolt) { +	case 0: +		/* Admittedly this one is an assumption. */ +		voltage_selector = 0; +		break; +	case 1800: +		voltage_selector = 1.8; +		break; +	case 2500: +		voltage_selector = 2.5; +		break; +	case 3500: +		voltage_selector = 3.5; +		break; +	default: +		msg_perr("Unknown voltage %i mV! Aborting.\n", millivolt); +		return 1; +	} +	msg_pdbg("Setting SPI voltage to %u.%03u V\n", millivolt / 1000, +		 millivolt % 1000); + +	uint8_t command[CMD_LENGTH] = { +		CMD_SET_VDD, +		voltage_selector * 2048 + 672, +		(voltage_selector * 2048 + 672) / 256, +		voltage_selector * 36, +		CMD_SET_VPP, +		0x40, +		voltage_selector * 18.61, +		voltage_selector * 13, +		CMD_END_OF_BUFFER +	}; + +	int ret = usb_interrupt_write(pickit2_handle, ENDPOINT_OUT, (char *)command, CMD_LENGTH, DFLT_TIMEOUT); + +	if (ret != CMD_LENGTH) { +		msg_perr("Command Set Voltage failed (%s)!\n", usb_strerror()); +		return 1; +	} + +	return 0; +} + +struct pickit2_spispeeds { +	const char *const name; +	const int speed; +}; + +static const struct pickit2_spispeeds spispeeds[] = { +	{ "1M",		0x1 }, +	{ "500k",	0x2 }, +	{ "333k",	0x3 }, +	{ "250k",	0x4 }, +	{ NULL,		0x0 }, +}; + +static int pickit2_set_spi_speed(unsigned int spispeed_idx) +{ +	msg_pdbg("SPI speed is %sHz\n", spispeeds[spispeed_idx].name); + +	uint8_t command[CMD_LENGTH] = { +		CMD_EXEC_SCRIPT, +		2, +		SCR_SET_ICSP_CLK_PERIOD, +		spispeed_idx, +		CMD_END_OF_BUFFER +	}; + +	int ret = usb_interrupt_write(pickit2_handle, ENDPOINT_OUT, (char *)command, CMD_LENGTH, DFLT_TIMEOUT); + +	if (ret != CMD_LENGTH) { +		msg_perr("Command Set SPI Speed failed (%s)!\n", usb_strerror()); +		return 1; +	} + +	return 0; +} + +static int pickit2_spi_send_command(struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, +				     const unsigned char *writearr, unsigned char *readarr) +{ + +	/* Maximum number of bytes per transaction (including command overhead) is 64. Lets play it safe +	 * and always assume the worst case scenario of 20 bytes command overhead. +	 */ +	if (writecnt + readcnt + 20 > CMD_LENGTH) { +		msg_perr("\nTotal packetsize (%i) is greater than 64 supported, aborting.\n", +			 writecnt + readcnt + 20); +		return 1; +	} + +	uint8_t buf[CMD_LENGTH] = {CMD_DOWNLOAD_DATA, writecnt}; +	int i = 2; +	for (; i < writecnt + 2; i++) { +		buf[i] = writearr[i - 2]; +	} + +	buf[i++] = CMD_CLR_ULOAD_BUFF; +	buf[i++] = CMD_EXEC_SCRIPT; + +	/* Determine script length based on number of bytes to be read or written */ +	if (writecnt == 1 && readcnt == 1) +		buf[i++] = 7; +	else if (writecnt == 1 || readcnt == 1) +		buf[i++] = 10; +	else +		buf[i++] = 13; +		 +	/* Assert CS# */ +	buf[i++] = SCR_VPP_OFF; +	buf[i++] = SCR_MCLR_GND_ON; + +	buf[i++] = SCR_SPI_WRITE_BUF; + +	if (writecnt > 1) { +		buf[i++] = SCR_LOOP; +		buf[i++] = 1; /* Loop back one instruction */ +		buf[i++] = writecnt - 1; /* Number of times to loop */ +	} + +	if (readcnt) +		buf[i++] = SCR_SPI_READ_BUF; + +	if (readcnt > 1) { +		buf[i++] = SCR_LOOP; +		buf[i++] = 1; /* Loop back one instruction */ +		buf[i++] = readcnt - 1; /* Number of times to loop */ +	} + +	/* De-assert CS# */ +	buf[i++] = SCR_MCLR_GND_OFF; +	buf[i++] = SCR_VPP_PWM_ON; +	buf[i++] = SCR_VPP_ON; + +	buf[i++] = CMD_UPLOAD_DATA; +	buf[i++] = CMD_END_OF_BUFFER; + +	int ret = usb_interrupt_write(pickit2_handle, ENDPOINT_OUT, (char *)buf, CMD_LENGTH, DFLT_TIMEOUT); + +	if (ret != CMD_LENGTH) { +		msg_perr("Send SPI failed, expected %i, got %i %s!\n", writecnt, ret, usb_strerror()); +		return 1; +	} + +	if (readcnt) { +		ret = usb_interrupt_read(pickit2_handle, ENDPOINT_IN, (char *)buf, CMD_LENGTH, DFLT_TIMEOUT); + +		if (ret != CMD_LENGTH) { +			msg_perr("Receive SPI failed, expected %i, got %i %s!\n", readcnt, ret, usb_strerror()); +			return 1; +		} + +		/* First byte indicates number of bytes transferred from upload buffer */ +		if (buf[0] != readcnt) { +			msg_perr("Unexpected number of bytes transferred, expected %i, got %i!\n", +				 readcnt, ret); +			return 1; +		} +		 +		/* Actual data starts at byte number two */ +		memcpy(readarr, &buf[1], readcnt); +	} + +	return 0; +} + +/* Copied from dediprog.c */ +/* Might be useful for other USB devices as well. static for now. */ +static int parse_voltage(char *voltage) +{ +	char *tmp = NULL; +	int i; +	int millivolt = 0, fraction = 0; + +	if (!voltage || !strlen(voltage)) { +		msg_perr("Empty voltage= specified.\n"); +		return -1; +	} +	millivolt = (int)strtol(voltage, &tmp, 0); +	voltage = tmp; +	/* Handle "," and "." as decimal point. Everything after it is assumed +	 * to be in decimal notation. +	 */ +	if ((*voltage == '.') || (*voltage == ',')) { +		voltage++; +		for (i = 0; i < 3; i++) { +			fraction *= 10; +			/* Don't advance if the current character is invalid, +			 * but continue multiplying. +			 */ +			if ((*voltage < '0') || (*voltage > '9')) +				continue; +			fraction += *voltage - '0'; +			voltage++; +		} +		/* Throw away remaining digits. */ +		voltage += strspn(voltage, "0123456789"); +	} +	/* The remaining string must be empty or "mV" or "V". */ +	tolower_string(voltage); + +	/* No unit or "V". */ +	if ((*voltage == '\0') || !strncmp(voltage, "v", 1)) { +		millivolt *= 1000; +		millivolt += fraction; +	} else if (!strncmp(voltage, "mv", 2) || +		   !strncmp(voltage, "millivolt", 9)) { +		/* No adjustment. fraction is discarded. */ +	} else { +		/* Garbage at the end of the string. */ +		msg_perr("Garbage voltage= specified.\n"); +		return -1; +	} +	return millivolt; +} + +static const struct spi_master spi_master_pickit2 = { +	.type		= SPI_CONTROLLER_PICKIT2, +	.max_data_read	= 40, +	.max_data_write	= 40, +	.command	= pickit2_spi_send_command, +	.multicommand	= default_spi_send_multicommand, +	.read		= default_spi_read, +	.write_256	= default_spi_write_256, +	.write_aai	= default_spi_write_aai, +}; + +static int pickit2_shutdown(void *data) +{ +	/* Set all pins to float and turn voltages off */ +	uint8_t command[CMD_LENGTH] = { +		CMD_EXEC_SCRIPT, +		8, +		SCR_SET_PINS, +		3, /* Bit-0=1(PDC In), Bit-1=1(PGD In), Bit-2=0(PDC LL), Bit-3=0(PGD LL) */ +		SCR_SET_AUX, +		1, /* Bit-0=1(Aux In), Bit-1=0(Aux LL) */ +		SCR_MCLR_GND_OFF, +		SCR_VPP_OFF, +		SCR_VDD_OFF, +		SCR_BUSY_LED_OFF, +		CMD_END_OF_BUFFER +	}; + +	int ret = usb_interrupt_write(pickit2_handle, ENDPOINT_OUT, (char *)command, CMD_LENGTH, DFLT_TIMEOUT); + +	if (ret != CMD_LENGTH) { +		msg_perr("Command Shutdown failed (%s)!\n", usb_strerror()); +		ret = 1; +	} +	if (usb_release_interface(pickit2_handle, 0) != 0) { +		msg_perr("Could not release USB interface!\n"); +		ret = 1; +	} +	if (usb_close(pickit2_handle) != 0) { +		msg_perr("Could not close USB device!\n"); +		ret = 1; +	} +	return ret; +} + +int pickit2_spi_init(void) +{ +	unsigned int usedevice = 0; // FIXME: allow to select one of multiple devices + +	uint8_t buf[CMD_LENGTH] = { +		CMD_EXEC_SCRIPT, +		10,			/* Script length */ +		SCR_SET_PINS, +		2, /* Bit-0=0(PDC Out), Bit-1=1(PGD In), Bit-2=0(PDC LL), Bit-3=0(PGD LL) */ +		SCR_SET_AUX, +		0, /* Bit-0=0(Aux Out), Bit-1=0(Aux LL) */ +		SCR_VDD_ON, +		SCR_MCLR_GND_OFF,	/* Let CS# float */ +		SCR_VPP_PWM_ON, +		SCR_VPP_ON,		/* Pull CS# high */ +		SCR_BUSY_LED_ON, +		CMD_CLR_DLOAD_BUFF, +		CMD_CLR_ULOAD_BUFF, +		CMD_END_OF_BUFFER +	}; + + +	int spispeed_idx = 0; +	char *spispeed = extract_programmer_param("spispeed"); +	if (spispeed != NULL) { +		int i = 0; +		for (; spispeeds[i].name; i++) { +			if (strcasecmp(spispeeds[i].name, spispeed) == 0) { +				spispeed_idx = i; +				break; +			} +		} +		if (spispeeds[i].name == NULL) { +			msg_perr("Error: Invalid 'spispeed' value.\n"); +			free(spispeed); +			return 1; +		} +		free(spispeed); +	} + +	int millivolt = 3500; +	char *voltage = extract_programmer_param("voltage"); +	if (voltage != NULL) { +		millivolt = parse_voltage(voltage); +		free(voltage); +		if (millivolt < 0) +			return 1; +	} + +	/* Here comes the USB stuff */ +	usb_init(); +	(void)usb_find_busses(); +	(void)usb_find_devices(); +	struct usb_device *dev = get_device_by_vid_pid(PICKIT2_VID, PICKIT2_PID, usedevice); +	if (dev == NULL) { +		msg_perr("Could not find a PICkit2 on USB!\n"); +		return 1; +	} +	msg_pdbg("Found USB device (%04x:%04x).\n", dev->descriptor.idVendor, dev->descriptor.idProduct); + +	pickit2_handle = usb_open(dev); +	int ret = usb_set_configuration(pickit2_handle, 1); +	if (ret != 0) { +		msg_perr("Could not set USB device configuration: %i %s\n", ret, usb_strerror()); +		if (usb_close(pickit2_handle) != 0) +			msg_perr("Could not close USB device!\n"); +		return 1; +	} +	ret = usb_claim_interface(pickit2_handle, 0); +	if (ret != 0) { +		msg_perr("Could not claim USB device interface %i: %i %s\n", 0, ret, usb_strerror()); +		if (usb_close(pickit2_handle) != 0) +			msg_perr("Could not close USB device!\n"); +		return 1; +	} + +	if (register_shutdown(pickit2_shutdown, NULL) != 0) { +		return 1; +	} + +	if (pickit2_get_firmware_version()) { +		return 1; +	} + +	/* Command Set SPI Speed */ +	if (pickit2_set_spi_speed(spispeed_idx)) { +		return 1; +	} + +	/* Command Set SPI Voltage */ +	msg_pdbg("Setting voltage to %i mV.\n", millivolt); +	if (pickit2_set_spi_voltage(millivolt) != 0) { +		return 1; +	} + +	/* Perform basic setup. +	 * Configure pin directions and logic levels, turn Vdd on, turn busy LED on and clear buffers. */ +	ret = usb_interrupt_write(pickit2_handle, ENDPOINT_OUT, (char *)buf, CMD_LENGTH, DFLT_TIMEOUT); +	if (ret != CMD_LENGTH) { +		msg_perr("Command Setup failed (%s)!\n", usb_strerror()); +		return 1; +	} + +	register_spi_master(&spi_master_pickit2); + +	return 0; +}  | 
