From e88650850b0406da92d8227057d7ad6131b6f56d Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Thu, 23 Apr 2020 18:43:04 +0800 Subject: [PATCH] successfully wrote data Signed-off-by: Sean Cross --- Cargo.lock | 84 +++++++++ Cargo.toml | 2 + ftdi-vcp-rs/src/lib.rs | 164 +++++++++++++++--- ftdi-vcp-rs/src/mpsse.rs | 141 +++++++++++++++ src/flash.rs | 364 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 213 +++++++++++++++++++++-- src/mpsse.rs | 0 7 files changed, 931 insertions(+), 37 deletions(-) create mode 100644 ftdi-vcp-rs/src/mpsse.rs create mode 100644 src/flash.rs create mode 100644 src/mpsse.rs diff --git a/Cargo.lock b/Cargo.lock index 605ba55..4ee79fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,9 +1,51 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "ftdi-prog" version = "0.1.0" dependencies = [ + "clap", "ftdi-vcp-rs", ] @@ -21,6 +63,48 @@ dependencies = [ "winapi", ] +[[package]] +name = "hermit-abi" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + [[package]] name = "winapi" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 3f3a4fe..bb8046c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,7 @@ edition = "2018" [dependencies] ftdi-vcp-rs = { path = "ftdi-vcp-rs" } +clap = "*" + [build-dependencies] # cc = { version = "1.0", features = ["parallel"] } diff --git a/ftdi-vcp-rs/src/lib.rs b/ftdi-vcp-rs/src/lib.rs index 90e252f..813df0e 100644 --- a/ftdi-vcp-rs/src/lib.rs +++ b/ftdi-vcp-rs/src/lib.rs @@ -1,13 +1,15 @@ use ftdi_vcp_sys::{ - FT_Close, FT_GetBitMode, FT_GetComPortNumber, FT_OpenEx, FT_Purge, FT_ResetDevice, - FT_SetBitMode, FT_Write, DWORD, FT_HANDLE, FT_OPEN_BY_DESCRIPTION, FT_STATUS, LONG, LPDWORD, - LPVOID, PVOID, UCHAR, + FT_Close, FT_GetBitMode, FT_GetComPortNumber, FT_GetLatencyTimer, FT_OpenEx, FT_Purge, FT_Read, + FT_ResetDevice, FT_SetBitMode, FT_SetLatencyTimer, FT_Write, DWORD, FT_HANDLE, + FT_OPEN_BY_DESCRIPTION, FT_STATUS, LONG, LPDWORD, LPVOID, PVOID, UCHAR, }; use std::convert::TryInto; use std::ffi::CString; use std::io::{Read, Write}; use std::mem::MaybeUninit; +pub mod mpsse; + #[derive(Debug)] pub enum BitMode { Reset, @@ -175,6 +177,7 @@ impl VCP { if result != Error::NoError { Err(result) } else { + self.bit_mode = bitmode; Ok(()) } } @@ -202,27 +205,126 @@ impl VCP { } } - pub fn write(&mut self, out_buffer: &[u8]) -> Result { - let mut bytes_written = MaybeUninit::::uninit(); - let result = Error::from(unsafe { - FT_Write( - self.handle, - out_buffer.as_ptr() as LPVOID, - out_buffer - .len() - .try_into() - .expect("couldn't convert buffer length to DWORD"), - bytes_written.as_mut_ptr() as LPDWORD, - ) - }); + pub fn latency_timer(&mut self) -> Result { + let mut latency = MaybeUninit::::uninit(); + let result = Error::from(unsafe { FT_GetLatencyTimer(self.handle, latency.as_mut_ptr()) }); if result != Error::NoError { Err(result) } else { - Ok(unsafe { bytes_written.assume_init() } - .try_into() - .expect("invalid number of bytes written")) + Ok(unsafe { latency.assume_init() }) } } + + pub fn set_latency_timer(&mut self, latency: u8) -> Result<(), Error> { + let result = Error::from(unsafe { FT_SetLatencyTimer(self.handle, latency) }); + if result != Error::NoError { + Err(result) + } else { + Ok(()) + } + } + + // pub fn write(&mut self, out_buffer: &[u8]) -> Result { + // let mut bytes_written = MaybeUninit::::uninit(); + // let result = Error::from(unsafe { + // FT_Write( + // self.handle, + // out_buffer.as_ptr() as LPVOID, + // out_buffer + // .len() + // .try_into() + // .expect("couldn't convert buffer length to DWORD"), + // bytes_written.as_mut_ptr() as LPDWORD, + // ) + // }); + // if result != Error::NoError { + // Err(result) + // } else { + // Ok(unsafe { bytes_written.assume_init() } + // .try_into() + // .expect("invalid number of bytes written")) + // } + // } + + pub fn set_gpio(&mut self, value: u8, direction: u8) -> Result<(), Error> { + match self.bit_mode { + BitMode::MPSSE => self + .write_all(&[mpsse::Command::MC_SETB_LOW.to_u8(), value, direction]) + .or_else(|_| Err(Error::IoError)), + _ => unimplemented!(), + } + } + + pub fn readb_low(&mut self) -> Result { + self.write_all(&[mpsse::Command::MC_READB_LOW.to_u8()]) + .or_else(|_| Err(Error::IoError))?; + let mut result = [0; 1]; + self.read(&mut result).or_else(|_| Err(Error::IoError))?; + Ok(result[0]) + } + + pub fn readb_high(&mut self) -> Result { + self.write_all(&[mpsse::Command::MC_READB_HIGH.to_u8()]) + .or_else(|_| Err(Error::IoError))?; + let mut result = [0; 1]; + self.read(&mut result).or_else(|_| Err(Error::IoError))?; + Ok(result[0]) + } + + /// The purpose of this function is unclear. It appears to send some number + /// of bits out the line. + pub fn xfer_spi_bits(&mut self, data: u8, bits: usize) -> Result { + if bits < 1 { + return Ok(0); + } + + let buffer = &[ + /* Input and output, update data on negative edge read on positive, bits. */ + 0x20 /*MC_DATA_IN*/ | 0x10 /*MC_DATA_OUT*/ | 0x01 /*MC_DATA_OCN*/ | 0x02, /*MC_DATA_BITS*/ + bits as u8 - 1, + data, + ]; + self.write_all(buffer).or_else(|_| Err(Error::IoError))?; + + let mut return_val = [0; 1]; + self.read_exact(&mut return_val) + .or_else(|_| Err(Error::IoError))?; + Ok(return_val[0]) + } + + pub fn xfer_spi(&mut self, data: &mut [u8]) -> Result<(), Error> { + if data.is_empty() { + return Ok(()); + } + + /* Input and output, update data on negative edge read on positive. */ + let buffer = &[ + 0x20 /*MC_DATA_IN*/ | 0x10 /*MC_DATA_OUT*/ | 0x01, /*MC_DATA_OCN*/ + (data.len() - 1) as u8, + ((data.len() - 1) / 256) as u8, + ]; + self.write_all(buffer).or_else(|_| Err(Error::IoError))?; + self.write_all(data).or_else(|_| Err(Error::IoError))?; + + self.read_exact(data).or_else(|_| Err(Error::IoError))?; + Ok(()) + } + + pub fn send_spi(&mut self, data: &[u8]) -> Result<(), Error> { + if data.is_empty() { + return Ok(()); + } + + /* Input and output, update data on negative edge read on positive. */ + let buffer = &[ + 0x10 /*MC_DATA_OUT*/ | 0x01, /*MC_DATA_OCN*/ + (data.len() - 1) as u8, + ((data.len() - 1) / 256) as u8, + ]; + self.write_all(buffer).or_else(|_| Err(Error::IoError))?; + self.write_all(data).or_else(|_| Err(Error::IoError))?; + Ok(()) + } } impl Write for VCP { @@ -241,7 +343,13 @@ impl Write for VCP { if result != Error::NoError { Err(std::io::Error::new(std::io::ErrorKind::Other, result)) } else { - Ok(unsafe { bytes_written.assume_init() } + let bytes_written = unsafe { bytes_written.assume_init() }; + // println!( + // "Wrote {} bytes (wanted to write {})", + // bytes_written, + // buf.len() + // ); + Ok(bytes_written .try_into() .expect("invalid number of bytes written")) } @@ -253,19 +361,23 @@ impl Write for VCP { impl Read for VCP { fn read(&mut self, buf: &mut [u8]) -> Result { - let mut bytes_written = MaybeUninit::::uninit(); + let mut bytes_read = MaybeUninit::::uninit(); let result = Error::from(unsafe { - FT_Write( + FT_Read( self.handle, - buf.as_ptr() as LPVOID, - 1, - bytes_written.as_mut_ptr() as LPDWORD, + buf.as_mut_ptr() as LPVOID, + buf.len() + .try_into() + .expect("couldn't convert buffer length to DWORD"), + bytes_read.as_mut_ptr() as LPDWORD, ) }); if result != Error::NoError { Err(std::io::Error::new(std::io::ErrorKind::Other, result)) } else { - Ok(unsafe { bytes_written.assume_init() } + let bytes_read = unsafe { bytes_read.assume_init() }; + // println!("Read {} bytes (wanted to read {})", bytes_read, buf.len()); + Ok(bytes_read .try_into() .expect("invalid number of bytes written")) } diff --git a/ftdi-vcp-rs/src/mpsse.rs b/ftdi-vcp-rs/src/mpsse.rs new file mode 100644 index 0000000..a5e3888 --- /dev/null +++ b/ftdi-vcp-rs/src/mpsse.rs @@ -0,0 +1,141 @@ + +// /* 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. +// */ +// pub enum CommandBits { + +// #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 engine command definitions */ + #[allow(non_camel_case_types)] +pub enum Command { + // Mode commands + /// Set Data bits LowByte + MC_SETB_LOW, + + /// Read Data bits LowByte + MC_READB_LOW, + /// Set Data bits HighByte + MC_SETB_HIGH, + /// Read data bits HighByte + MC_READB_HIGH, + /// Enable loopback + MC_LOOPBACK_EN, + /// Disable loopback + MC_LOOPBACK_DIS, + /// Set clock divisor + MC_SET_CLK_DIV, + /// Flush buffer fifos to the PC. + MC_FLUSH, + /// Wait on GPIOL1 to go high. + MC_WAIT_H, + /// Wait on GPIOL1 to go low. + MC_WAIT_L, + /// Disable /5 div, enables 60MHz master clock + MC_TCK_X5, + /// Enable /5 div, backward compat to FT2232D + MC_TCK_D5, + /// Enable 3 phase clk, DDR I2C + MC_EN_3PH_CLK, + /// Disable 3 phase clk + MC_DIS_3PH_CLK, + /// Clock every bit, used for JTAG + MC_CLK_N, + /// Clock every byte, used for JTAG + MC_CLK_N8, + /// Clock until GPIOL1 goes high + MC_CLK_TO_H, + /// Clock until GPIOL1 goes low + MC_CLK_TO_L, + /// Enable adaptive clocking + MC_EN_ADPT_CLK, + /// Disable adaptive clocking + MC_DIS_ADPT_CLK, + /// Clock until GPIOL1 goes high, count bytes + MC_CLK8_TO_H, + /// Clock until GPIOL1 goes low, count bytes + MC_CLK8_TO_L, + /// Set IO to only drive on 0 and tristate on 1 + MC_TRI, + /// CPU mode commands + /// CPUMode read short address + MC_CPU_RS, + /// CPUMode read extended address + MC_CPU_RE, + /// CPUMode write short address + MC_CPU_WS, + /// CPUMode write extended address + MC_CPU_WE, +} + +impl Command { + pub fn to_u8(&self) -> u8{ + use Command::*; + match *self { + /* 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 */ + } + } +} diff --git a/src/flash.rs b/src/flash.rs new file mode 100644 index 0000000..54be22b --- /dev/null +++ b/src/flash.rs @@ -0,0 +1,364 @@ +use ftdi_vcp_rs::{mpsse::Command::*, BitMode, Error, VCP}; +use std::thread::sleep; +use std::time::Duration; + +pub enum EraseType { + Kb64, +} + +pub struct Flash { + pub vcp: VCP, +} + +impl Flash { + pub fn new(vcp: VCP) -> Flash { + Flash { vcp } + } + + fn set_cs_creset(&mut self, cs_asserted: bool, reset_asserted: bool) -> Result<(), Error> { + let gpio = if cs_asserted { 0x10 } else { 0 } | if reset_asserted { 0x80 } else { 0 }; + let direction = 0x93; + self.vcp.set_gpio(gpio, direction) + } + + // the FPGA reset is released so also FLASH chip select should be deasserted + pub fn release_reset(&mut self) -> Result<(), Error> { + self.set_cs_creset(true, true) + } + + // FLASH chip select assert + // should only happen while FPGA reset is asserted + pub fn chip_select(&mut self) -> Result<(), Error> { + self.set_cs_creset(false, false) + } + + // FLASH chip select deassert + pub fn chip_deselect(&mut self) -> Result<(), Error> { + self.set_cs_creset(true, false) + } + + pub fn reset(&mut self) -> Result<(), ftdi_vcp_rs::Error> { + self.chip_select()?; + self.vcp.xfer_spi_bits(0xFF, 8)?; + self.chip_deselect()?; + + self.chip_select()?; + self.vcp.xfer_spi_bits(0xFF, 2)?; + self.chip_deselect()?; + Ok(()) + } + + pub fn power_up(&mut self) -> Result<(), Error> { + let mut cmd = [0xAB /* FC_RPD */]; + self.chip_select()?; + self.vcp.xfer_spi(&mut cmd)?; + self.chip_deselect()?; + Ok(()) + } + + pub fn power_down(&mut self) -> Result<(), Error> { + let mut cmd = [0xB9 /* FC_PD */]; + self.chip_select()?; + self.vcp.xfer_spi(&mut cmd)?; + self.chip_deselect()?; + Ok(()) + } + + pub fn read_id(&mut self, verbose: bool) -> Result<(), Error> { + /* JEDEC ID structure: + * Byte No. | Data Type + * ---------+---------- + * 0 | FC_JEDECID Request Command + * 1 | MFG ID + * 2 | Dev ID 1 + * 3 | Dev ID 2 + * 4 | Ext Dev Str Len + */ + let mut data = [0xffu8; 260]; + data[0] = 0x9Fu8 /* FC_JEDECID - Read JEDEC ID */; + let mut len = 5usize; // command + 4 response bytes + + if verbose { + println!("read flash ID.."); + } + + self.chip_select()?; + + // Write command and read first 4 bytes + self.vcp.xfer_spi(&mut data[0..=4])?; + + if data[4] == 0xFF { + println!( + "Extended Device String Length is 0xFF, this is likely a read error. Ignorig..." + ); + } else { + // Read extended JEDEC ID bytes + if data[4] != 0 { + len += data[4] as usize; + let (_, mut jedec_data) = data.split_at_mut(4); + self.vcp.xfer_spi(&mut jedec_data)?; + } + } + + self.chip_deselect()?; + + // TODO: Add full decode of the JEDEC ID. + print!("flash ID:"); + for b in &data[1..len] { + print!(" 0x{:02X}", b); + } + println!(); + Ok(()) + } + + pub fn cdone(&mut self) -> Result { + // ADBUS6 (GPIOL2) + match self.vcp.readb_low()? & 0x40 { + 0 => Ok(false), + _ => Ok(true), + } + } + + pub fn cdone_str(&mut self) -> Result<&'static str, Error> { + if self.cdone()? { + Ok("high") + } else { + Ok("low") + } + } + + pub fn write_enable(&mut self, verbose: bool) -> Result<(), Error> { + if verbose { + println!("status before enable:"); + self.read_status(verbose)?; + println!("write enable.."); + } + + let mut data = [0x06 /* FC_WE // Write Enable */]; + self.chip_select()?; + self.vcp.xfer_spi(&mut data)?; + self.chip_deselect()?; + + if verbose { + println!("status after enable:"); + self.read_status(verbose)?; + } + Ok(()) + } + + pub fn bulk_erase(&mut self) -> Result<(), Error> { + println!("bulk erase.."); + let mut data = [0xC7 /* FC_CE // Chip Erase */]; + self.chip_select()?; + self.vcp.xfer_spi(&mut data)?; + self.chip_deselect()?; + Ok(()) + } + + pub fn sector_erase(&mut self, erase_type: EraseType, addr: usize) -> Result<(), Error> { + let erase_cmd = match erase_type { + EraseType::Kb64 => 0xD8, /* FC_BE64 // Block Erase 64kb */ + }; + + println!("erase 64kB sector at 0x{:06X}..", addr); + self.chip_select()?; + self.vcp + .send_spi(&[erase_cmd, (addr >> 16) as u8, (addr >> 8) as u8, addr as u8])?; + self.chip_deselect()?; + Ok(()) + } + + pub fn read_status(&mut self, verbose: bool) -> Result { + let mut data = [0x05 /* FC_RSR1 // Read Status Register 1 */, 0x00]; + + self.chip_select()?; + self.vcp.xfer_spi(&mut data)?; + self.chip_deselect()?; + + if verbose { + println!("SR1: 0x{:02X}", data[1]); + println!( + " - SPRL: {}", + if data[1] & (1 << 7) == 0 { + "unlocked" + } else { + "locked" + } + ); + println!( + " - SPM: {}", + if data[1] & (1 << 6) == 0 { + "Byte/Page Prog Mode" + } else { + "Sequential Prog Mode" + } + ); + println!( + " - EPE: {}\n", + if data[1] & (1 << 5) == 0 { + "Erase/Prog success" + } else { + "Erase/Prog error" + } + ); + println!( + "- SPM: {}\n", + if data[1] & (1 << 4) == 0 { + "~WP asserted" + } else { + "~WP deasserted" + } + ); + println!( + " - SWP: {}", + match (data[1] >> 2) & 0x3 { + 0 => "All sectors unprotected", + 1 => "Some sectors protected", + 2 => "Reserved (xxxx 10xx)", + 3 => "All sectors protected", + _ => panic!("math is broken!"), + } + ); + println!( + " - WEL: {}", + if (data[1] & (1 << 1)) == 0 { + "Not write enabled" + } else { + "Write enabled" + } + ); + println!( + " - ~RDY: {}", + if (data[1] & (1 << 0)) == 0 { + "Ready" + } else { + "Busy" + } + ); + } + + sleep(Duration::from_micros(1_000)); + + Ok(data[1]) + } + + pub fn disable_protection(&mut self) -> Result<(), Error> { + println!("disable flash protection..."); + + // Write Status Register 1 <- 0x00 + let mut data = [0x01 /* FC_WSR1 // Write Status Register 1 */, 0x00]; + self.chip_select()?; + self.vcp.xfer_spi(&mut data)?; + self.chip_deselect()?; + + self.wait(false)?; + + // Read Status Register 1 + data[0] = 0x05; // FC_RSR1; + + self.chip_select()?; + self.vcp.xfer_spi(&mut data)?; + self.chip_deselect()?; + + if data[1] != 0x00 { + println!( + "failed to disable protection, SR now equal to 0x{:02x} (expected 0x00)\n", + data[1] + ); + } + + Ok(()) + } + + pub fn wait(&mut self, verbose: bool) -> Result<(), Error> { + if verbose { + println!("waiting.."); + } + + let mut count = 0; + loop { + let mut data = [0x05 /* FC_RSR1 // Read Status Register 1 */, 0x00]; + + self.chip_select()?; + self.vcp.xfer_spi(&mut data)?; + self.chip_deselect()?; + + if (data[1] & 0x01) == 0 { + if count < 2 { + count += 1; + if verbose { + print!("r"); + //fflush(stderr); + } + } else { + if verbose { + print!("R"); + // fflush(stderr); + } + break; + } + } else { + if verbose { + print!("."); + // fflush(stderr); + } + count = 0; + } + + sleep(Duration::from_micros(1_000)); + } + + if verbose { + println!(); + } + + Ok(()) + } + + pub fn prog(&mut self, addr: usize, data: &[u8], verbose: bool) -> Result<(), Error> { + if verbose { + println!("prog 0x{:06X} +0x{:03X}..", addr, data.len()); + } + + let command = [ + 0x02, /* FC_PP // Page Program */ + (addr >> 16) as u8, + (addr >> 8) as u8, + addr as u8, + ]; + + self.chip_select()?; + self.vcp.send_spi(&command)?; + self.vcp.send_spi(data)?; + self.chip_deselect()?; + + // if verbose { + // for (int i = 0; i < n; i++) + // fprintf(stderr, "%02x%c", data[i], i == n - 1 || i % 32 == 31 ? '\n' : ' '); + // } + Ok(()) + } + + pub fn read(&mut self, addr: usize, data: &mut [u8], verbose: bool) -> Result<(), Error> { + if verbose { + println!("read 0x{:06X} +0x{:03X}..", addr, data.len()); + } + + let command = [ + 0x03, /*FC_RD // Read Data */ + (addr >> 16) as u8, + (addr >> 8) as u8, + addr as u8, + ]; + + self.chip_select()?; + self.vcp.send_spi(&command)?; + self.vcp.xfer_spi(data)?; + self.chip_deselect()?; + + // if (verbose) + // for (int i = 0; i < n; i++) + // fprintf(stderr, "%02x%c", data[i], i == n - 1 || i % 32 == 31 ? '\n' : ' '); + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index fe35d79..a4910f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ - /* #define FT_LIST_NUMBER_ONLY 0x80000000 #define FT_LIST_BY_INDEX 0x40000000 @@ -6,24 +5,216 @@ #define FT_LIST_MASK (FT_LIST_NUMBER_ONLY|FT_LIST_BY_INDEX|FT_LIST_ALL) */ -use ftdi_vcp_rs::{VCP, BitMode}; +use clap::{App, Arg, SubCommand}; +use ftdi_vcp_rs::{mpsse::Command::*, BitMode, VCP}; +use std::io::{Write, Read}; use std::thread::sleep; use std::time::Duration; +use std::fs::File; + +mod flash; fn main() -> Result<(), ftdi_vcp_rs::Error> { let mut vcp = VCP::new_from_name("iCEBreaker V1.0e A").expect("couldn't open vcp"); + let slow_clock = false; + let read_mode = false; + let check_mode = false; + let dont_erase = false; + let bulk_erase = false; + let disable_verify = false; + let erase_mode = false; + let rw_offset = 0; + let disable_protect = false; + let read_size = 256 * 1024; + + let mut bitstream = vec![]; + + let matches = App::new("iCE40 Programmer") + .version("1.0") + .author("Sean Cross ") + .about("Port of Iceprog") + .arg( + Arg::with_name("FILENAME") + .help("Sets the bitstream file to read or write") + .required(true) + .index(1), + ) + .arg( + Arg::with_name("v") + .short("v") + .multiple(true) + .help("Sets the level of verbosity"), + ) + .arg( + Arg::with_name("test") + .short("t") + .help("Run tests"), + ) + .get_matches(); + + let verbose = matches.is_present("verbose"); + let test_mode = matches.is_present("test"); + + let bitstream = { + let filename = matches.value_of("FILENAME").unwrap(); + let mut file = File::open(filename).expect("Couldn't open path"); + file.read_to_end(&mut bitstream).expect("couldn't read to end"); + bitstream + }; + println!("Opened VCP: {:?}", vcp); vcp.reset()?; - vcp.set_bitmode(0x80, BitMode::SyncBitbang)?; + vcp.purge()?; - for i in 0..10 { - if i & 1 != 0 { - vcp.write(&[0x80])?; - } else { - vcp.write(&[0x00])?; - } - sleep(Duration::from_millis(500)); + let previous_latency = vcp.latency_timer()?; + vcp.set_latency_timer(1)?; + + vcp.set_bitmode(0xff, BitMode::MPSSE)?; + + // enable clock divide by 5 + vcp.write(&[MC_TCK_D5.to_u8()]) + .or_else(|_| Err(ftdi_vcp_rs::Error::IoError))?; + + if slow_clock { + // set 50 kHz clock + vcp.write(&[MC_SET_CLK_DIV.to_u8(), 119, 0x00]) + .or_else(|_| Err(ftdi_vcp_rs::Error::IoError))?; + } else { + // set 6 MHz clock + vcp.write(&[MC_SET_CLK_DIV.to_u8(), 0x00, 0x00]) + .or_else(|_| Err(ftdi_vcp_rs::Error::IoError))?; } - println!("VCP COM{}:", vcp.com_port()?); + + let mut flash = flash::Flash::new(vcp); + flash.release_reset()?; + + sleep(Duration::from_micros(100_000)); + + if test_mode { + println!("reset.."); + + flash.chip_deselect()?; + sleep(Duration::from_micros(250_000)); + + println!("cdone: {}", flash.cdone_str()?); + + flash.reset()?; + flash.power_up()?; + + flash.read_id(true)?; + + flash.power_down()?; + + flash.release_reset()?; + sleep(Duration::from_micros(250_000)); + + println!("cdone: {}", flash.cdone_str()?); + } else { + // --------------------------------------------------------- + // Reset + // --------------------------------------------------------- + + println!("reset.."); + + flash.chip_deselect()?; + sleep(Duration::from_micros(250_000)); + + println!("cdone: {}", flash.cdone_str()?); + + flash.reset()?; + flash.power_up()?; + + flash.read_id(true)?; + + // --------------------------------------------------------- + // Program + // --------------------------------------------------------- + + if !read_mode && !check_mode { + if disable_protect { + flash.write_enable(verbose)?; + flash.disable_protection()?; + } + + if !dont_erase { + if bulk_erase { + flash.write_enable(verbose)?; + flash.bulk_erase()?; + flash.wait(verbose)?; + } else { + println!("file size: {}", bitstream.len()); + + let begin_addr = rw_offset & !0xffff; + let end_addr = (rw_offset + bitstream.len() + 0xffff) & !0xffff; + + for addr in (begin_addr..end_addr).step_by(0x10000) { + flash.write_enable(verbose)?; + flash.sector_erase(flash::EraseType::Kb64, addr)?; + if verbose { + println!("Status after block erase:"); + flash.read_status(verbose)?; + } + flash.wait(verbose)?; + } + } + } + + if !erase_mode { + println!("programming.."); + + for (idx, page) in bitstream.chunks(256).enumerate() { + flash.write_enable(verbose)?; + flash.prog(rw_offset + idx*256, page, verbose)?; + flash.wait(verbose)?; + } + + /* seek to the beginning for second pass */ + // fseek(f, 0, SEEK_SET); + } + } + + // --------------------------------------------------------- + // Read/Verify + // --------------------------------------------------------- + + if read_mode { + println!("reading.."); + for addr in (0..read_size).step_by(256) { + // uint8_t buffer[256]; + // flash_read(rw_offset + addr, buffer, 256); + // fwrite(buffer, read_size - addr > 256 ? 256 : read_size - addr, 1, f); + } + } else if !erase_mode && !disable_verify { + println!("reading.."); + for (idx, page) in bitstream.chunks(256).enumerate() { + let mut buffer_flash = [0; 256]; + flash.read(rw_offset + idx*256, &mut buffer_flash, verbose); + if ! page.iter().zip(buffer_flash.iter()).all(|(a,b)| a == b) { + println!("Found difference between flash and file!"); + } + } + + println!("VERIFY OK"); + } + + // --------------------------------------------------------- + // Reset + // --------------------------------------------------------- + + flash.power_down()?; + + flash.release_reset(); + sleep(Duration::from_micros(250_000)); + + println!("cdone: {}", flash.cdone_str()?); + } + + if let Ok(com_port) = flash.vcp.com_port() { + println!("VCP COM{}:", com_port); + } else { + println!("No COM port assigned"); + } + + flash.vcp.set_latency_timer(previous_latency)?; Ok(()) } diff --git a/src/mpsse.rs b/src/mpsse.rs new file mode 100644 index 0000000..e69de29