diff options
Diffstat (limited to 'iceprog/mpsse.c')
| -rw-r--r-- | iceprog/mpsse.c | 369 | 
1 files changed, 369 insertions, 0 deletions
diff --git a/iceprog/mpsse.c b/iceprog/mpsse.c new file mode 100644 index 0000000..c26fce3 --- /dev/null +++ b/iceprog/mpsse.c @@ -0,0 +1,369 @@ +/* + *  iceprog -- simple programming tool for FTDI-based Lattice iCE programmers + * + *  Copyright (C) 2015  Clifford Wolf <clifford@clifford.at> + *  Copyright (C) 2018  Piotr Esden-Tempski <piotr@esden.net> + * + *  Permission to use, copy, modify, and/or distribute this software for any + *  purpose with or without fee is hereby granted, provided that the above + *  copyright notice and this permission notice appear in all copies. + * + *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *  Relevant Documents: + *  ------------------- + *  http://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf + */ + +#define _GNU_SOURCE + +#include <ftdi.h> +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> + +#include "mpsse.h" + +// --------------------------------------------------------- +// MPSSE / FTDI definitions +// --------------------------------------------------------- + +/* FTDI bank pinout typically used for iCE dev boards + * BUS IO | Signal | Control + * -------+--------+-------------- + * xDBUS0 |    SCK | MPSSE + * xDBUS1 |   MOSI | MPSSE + * xDBUS2 |   MISO | MPSSE + * xDBUS3 |     nc | + * xDBUS4 |     CS | GPIO + * xDBUS5 |     nc | + * xDBUS6 |  CDONE | GPIO + * xDBUS7 | CRESET | GPIO + */ + +struct ftdi_context mpsse_ftdic; +bool mpsse_ftdic_open = false; +bool mpsse_ftdic_latency_set = false; +unsigned char mpsse_ftdi_latency; + +/* MPSSE engine command definitions */ +enum mpsse_cmd +{ +	/* Mode commands */ +	MC_SETB_LOW = 0x80, /* Set Data bits LowByte */ +	MC_READB_LOW = 0x81, /* Read Data bits LowByte */ +	MC_SETB_HIGH = 0x82, /* Set Data bits HighByte */ +	MC_READB_HIGH = 0x83, /* Read data bits HighByte */ +	MC_LOOPBACK_EN = 0x84, /* Enable loopback */ +	MC_LOOPBACK_DIS = 0x85, /* Disable loopback */ +	MC_SET_CLK_DIV = 0x86, /* Set clock divisor */ +	MC_FLUSH = 0x87, /* Flush buffer fifos to the PC. */ +	MC_WAIT_H = 0x88, /* Wait on GPIOL1 to go high. */ +	MC_WAIT_L = 0x89, /* Wait on GPIOL1 to go low. */ +	MC_TCK_X5 = 0x8A, /* Disable /5 div, enables 60MHz master clock */ +	MC_TCK_D5 = 0x8B, /* Enable /5 div, backward compat to FT2232D */ +	MC_EN_3PH_CLK = 0x8C, /* Enable 3 phase clk, DDR I2C */ +	MC_DIS_3PH_CLK = 0x8D, /* Disable 3 phase clk */ +	MC_CLK_N = 0x8E, /* Clock every bit, used for JTAG */ +	MC_CLK_N8 = 0x8F, /* Clock every byte, used for JTAG */ +	MC_CLK_TO_H = 0x94, /* Clock until GPIOL1 goes high */ +	MC_CLK_TO_L = 0x95, /* Clock until GPIOL1 goes low */ +	MC_EN_ADPT_CLK = 0x96, /* Enable adaptive clocking */ +	MC_DIS_ADPT_CLK = 0x97, /* Disable adaptive clocking */ +	MC_CLK8_TO_H = 0x9C, /* Clock until GPIOL1 goes high, count bytes */ +	MC_CLK8_TO_L = 0x9D, /* Clock until GPIOL1 goes low, count bytes */ +	MC_TRI = 0x9E, /* Set IO to only drive on 0 and tristate on 1 */ +	/* CPU mode commands */ +	MC_CPU_RS = 0x90, /* CPUMode read short address */ +	MC_CPU_RE = 0x91, /* CPUMode read extended address */ +	MC_CPU_WS = 0x92, /* CPUMode write short address */ +	MC_CPU_WE = 0x93, /* CPUMode write extended address */ +}; + +/* Transfer Command bits */ + +/* All byte based commands consist of: + * - Command byte + * - Length lsb + * - Length msb + * + * If data out is enabled the data follows after the above command bytes, + * otherwise no additional data is needed. + * - Data * n + * + * All bit based commands consist of: + * - Command byte + * - Length + * + * If data out is enabled a byte containing bitst to transfer follows. + * Otherwise no additional data is needed. Only up to 8 bits can be transferred + * per transaction when in bit mode. + */ + +/* b 0000 0000 + *   |||| |||`- Data out negative enable. Update DO on negative clock edge. + *   |||| ||`-- Bit count enable. When reset count represents bytes. + *   |||| |`--- Data in negative enable. Latch DI on negative clock edge. + *   |||| `---- LSB enable. When set clock data out LSB first. + *   |||| + *   |||`------ Data out enable + *   ||`------- Data in enable + *   |`-------- TMS mode enable + *   `--------- Special command mode enable. See mpsse_cmd enum. + */ + +#define MC_DATA_TMS  (0x40) /* When set use TMS mode */ +#define MC_DATA_IN   (0x20) /* When set read data (Data IN) */ +#define MC_DATA_OUT  (0x10) /* When set write data (Data OUT) */ +#define MC_DATA_LSB  (0x08) /* When set input/output data LSB first. */ +#define MC_DATA_ICN  (0x04) /* When set receive data on negative clock edge */ +#define MC_DATA_BITS (0x02) /* When set count bits not bytes */ +#define MC_DATA_OCN  (0x01) /* When set update data on negative clock edge */ + +// --------------------------------------------------------- +// MPSSE / FTDI function implementations +// --------------------------------------------------------- + +void mpsse_check_rx() +{ +	while (1) { +		uint8_t data; +		int rc = ftdi_read_data(&mpsse_ftdic, &data, 1); +		if (rc <= 0) +			break; +		fprintf(stderr, "unexpected rx byte: %02X\n", data); +	} +} + +void mpsse_error(int status) +{ +	mpsse_check_rx(); +	fprintf(stderr, "ABORT.\n"); +	if (mpsse_ftdic_open) { +		if (mpsse_ftdic_latency_set) +			ftdi_set_latency_timer(&mpsse_ftdic, mpsse_ftdi_latency); +		ftdi_usb_close(&mpsse_ftdic); +	} +	ftdi_deinit(&mpsse_ftdic); +	exit(status); +} + +uint8_t mpsse_recv_byte() +{ +	uint8_t data; +	while (1) { +		int rc = ftdi_read_data(&mpsse_ftdic, &data, 1); +		if (rc < 0) { +			fprintf(stderr, "Read error.\n"); +			mpsse_error(2); +		} +		if (rc == 1) +			break; +		usleep(100); +	} +	return data; +} + +void mpsse_send_byte(uint8_t data) +{ +	int rc = ftdi_write_data(&mpsse_ftdic, &data, 1); +	if (rc != 1) { +		fprintf(stderr, "Write error (single byte, rc=%d, expected %d).\n", rc, 1); +		mpsse_error(2); +	} +} + +void mpsse_send_spi(uint8_t *data, int n) +{ +	if (n < 1) +		return; + +	/* Output only, update data on negative clock edge. */ +	mpsse_send_byte(MC_DATA_OUT | MC_DATA_OCN); +	mpsse_send_byte(n - 1); +	mpsse_send_byte((n - 1) >> 8); + +	int rc = ftdi_write_data(&mpsse_ftdic, data, n); +	if (rc != n) { +		fprintf(stderr, "Write error (chunk, rc=%d, expected %d).\n", rc, n); +		mpsse_error(2); +	} +} + +void mpsse_xfer_spi(uint8_t *data, int n) +{ +	if (n < 1) +		return; + +	/* Input and output, update data on negative edge read on positive. */ +	mpsse_send_byte(MC_DATA_IN | MC_DATA_OUT | MC_DATA_OCN); +	mpsse_send_byte(n - 1); +	mpsse_send_byte((n - 1) >> 8); + +	int rc = ftdi_write_data(&mpsse_ftdic, data, n); +	if (rc != n) { +		fprintf(stderr, "Write error (chunk, rc=%d, expected %d).\n", rc, n); +		mpsse_error(2); +	} + +	for (int i = 0; i < n; i++) +		data[i] = mpsse_recv_byte(); +} + +uint8_t mpsse_xfer_spi_bits(uint8_t data, int n) +{ +	if (n < 1) +		return 0; + +	/* Input and output, update data on negative edge read on positive, bits. */ +	mpsse_send_byte(MC_DATA_IN | MC_DATA_OUT | MC_DATA_OCN | MC_DATA_BITS); +	mpsse_send_byte(n - 1); +	mpsse_send_byte(data); + +	return mpsse_recv_byte(); +} + +void mpsse_set_gpio(int slavesel_b, int creset_b) +{ +	uint8_t gpio = 0; + +	if (slavesel_b) { +		// ADBUS4 (GPIOL0) +		gpio |= 0x10; +	} + +	if (creset_b) { +		// ADBUS7 (GPIOL3) +		gpio |= 0x80; +	} + +	mpsse_send_byte(MC_SETB_LOW); +	mpsse_send_byte(gpio); /* Value */ +	mpsse_send_byte(0x93); /* Direction */ +} + +int mpsse_get_cdone() +{ +	uint8_t data; +	mpsse_send_byte(MC_READB_LOW); +	data = mpsse_recv_byte(); +	// ADBUS6 (GPIOL2) +	return (data & 0x40) != 0; +} + + +void mpsse_send_dummy_bytes(uint8_t n) +{ +	// add 8 x count dummy bits (aka n bytes) +	mpsse_send_byte(MC_CLK_N8); +	mpsse_send_byte(n - 1); +	mpsse_send_byte(0x00); + +} + +void mpsse_send_dummy_bit(void) +{ +	// add 1  dummy bit +	mpsse_send_byte(MC_CLK_N); +	mpsse_send_byte(0x00); +} + +void mpsse_init(int ifnum, const char *devstr, bool slow_clock) +{ +	enum ftdi_interface ftdi_ifnum = INTERFACE_A; + +	switch (ifnum) { +		case 0: +			ftdi_ifnum = INTERFACE_A; +			break; +		case 1: +			ftdi_ifnum = INTERFACE_B; +			break; +		case 2: +			ftdi_ifnum = INTERFACE_C; +			break; +		case 3: +			ftdi_ifnum = INTERFACE_D; +			break; +		default: +			ftdi_ifnum = INTERFACE_A; +			break; +	} + +	ftdi_init(&mpsse_ftdic); +	ftdi_set_interface(&mpsse_ftdic, ftdi_ifnum); + +	if (devstr != NULL) { +		if (ftdi_usb_open_string(&mpsse_ftdic, devstr)) { +			fprintf(stderr, "Can't find iCE FTDI USB device (device string %s).\n", devstr); +			mpsse_error(2); +		} +	} else { +		if (ftdi_usb_open(&mpsse_ftdic, 0x0403, 0x6010) && ftdi_usb_open(&mpsse_ftdic, 0x0403, 0x6014)) { +			fprintf(stderr, "Can't find iCE FTDI USB device (vendor_id 0x0403, device_id 0x6010 or 0x6014).\n"); +			mpsse_error(2); +		} +	} + +	mpsse_ftdic_open = true; + +	if (ftdi_usb_reset(&mpsse_ftdic)) { +		fprintf(stderr, "Failed to reset iCE FTDI USB device.\n"); +		mpsse_error(2); +	} + +	if (ftdi_usb_purge_buffers(&mpsse_ftdic)) { +		fprintf(stderr, "Failed to purge buffers on iCE FTDI USB device.\n"); +		mpsse_error(2); +	} + +	if (ftdi_get_latency_timer(&mpsse_ftdic, &mpsse_ftdi_latency) < 0) { +		fprintf(stderr, "Failed to get latency timer (%s).\n", ftdi_get_error_string(&mpsse_ftdic)); +		mpsse_error(2); +	} + +	/* 1 is the fastest polling, it means 1 kHz polling */ +	if (ftdi_set_latency_timer(&mpsse_ftdic, 1) < 0) { +		fprintf(stderr, "Failed to set latency timer (%s).\n", ftdi_get_error_string(&mpsse_ftdic)); +		mpsse_error(2); +	} + +	mpsse_ftdic_latency_set = true; + +	/* Enter MPSSE (Multi-Protocol Synchronous Serial Engine) mode. Set all pins to output. */ +	if (ftdi_set_bitmode(&mpsse_ftdic, 0xff, BITMODE_MPSSE) < 0) { +		fprintf(stderr, "Failed to set BITMODE_MPSSE on iCE FTDI USB device.\n"); +		mpsse_error(2); +	} + +	// enable clock divide by 5 +	mpsse_send_byte(MC_TCK_D5); + +	if (slow_clock) { +		// set 50 kHz clock +		mpsse_send_byte(MC_SET_CLK_DIV); +		mpsse_send_byte(119); +		mpsse_send_byte(0x00); +	} else { +		// set 6 MHz clock +		mpsse_send_byte(MC_SET_CLK_DIV); +		mpsse_send_byte(0x00); +		mpsse_send_byte(0x00); +	} +} + +void mpsse_close(void) +{ +	ftdi_set_latency_timer(&mpsse_ftdic, mpsse_ftdi_latency); +	ftdi_disable_bitbang(&mpsse_ftdic); +	ftdi_usb_close(&mpsse_ftdic); +	ftdi_deinit(&mpsse_ftdic); +}
\ No newline at end of file  | 
