aboutsummaryrefslogtreecommitdiffstats
path: root/iceprog/mpsse.c
diff options
context:
space:
mode:
authorPiotr Esden-Tempski <piotr@esden.net>2018-12-24 09:10:59 +0100
committerPiotr Esden-Tempski <piotr@esden.net>2018-12-24 09:10:59 +0100
commit61bc31bbdc359571e4315e197dbee9b00d739aac (patch)
treeb41d28c2ea32034cc29d018de0982fc1a80dec20 /iceprog/mpsse.c
parent9671b760f84ca4006f0ef101a3e3b201df4eabb5 (diff)
downloadicestorm-61bc31bbdc359571e4315e197dbee9b00d739aac.tar.gz
icestorm-61bc31bbdc359571e4315e197dbee9b00d739aac.tar.bz2
icestorm-61bc31bbdc359571e4315e197dbee9b00d739aac.zip
Factored out mpsse functions for easier reusability.
Diffstat (limited to 'iceprog/mpsse.c')
-rw-r--r--iceprog/mpsse.c369
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