364
src/flash.rs
Normal file
364
src/flash.rs
Normal file
@ -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<bool, Error> {
|
||||
// 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<u8, Error> {
|
||||
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(())
|
||||
}
|
||||
}
|
213
src/main.rs
213
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 <sean@xobs.io>")
|
||||
.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(())
|
||||
}
|
||||
|
0
src/mpsse.rs
Normal file
0
src/mpsse.rs
Normal file
Reference in New Issue
Block a user