flash/nor/jtagspi: add JTAGSPI driver
Many FPGA board speak JTAG and have a SPI flash for their bitstream attached to them. The SPI flash is programmed by first uploading a proxy bitstream to the FPGA that connects the JTAG interface to the SPI interface if the IR contains a certain USER instruction. Then the SPI flash can be erase, written, read directly through the JTAG DR. The JTAG and SPI signaling is compatible. Such a proxy bitstream only needs to connect TDO-MISO, TDI-MOSI, TCK-CLK, and the activate the chip select when the IR contains the special instruction and the JTAG state machine is in the DR-SHIFT state. Change-Id: Ibc21d793a83b36fa37e2704966aa5c837c4dd0d2 Signed-off-by: Robert Jordens <jordens@gmail.com> Reviewed-on: http://openocd.zylin.com/2844 Tested-by: jenkins Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk>
This commit is contained in:
parent
3edcb94186
commit
d25355473d
|
@ -0,0 +1,317 @@
|
|||
#!/usr/bin/python3
|
||||
#
|
||||
# Copyright (C) 2015 Robert Jordens <jordens@gmail.com>
|
||||
#
|
||||
# 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; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from migen.fhdl.std import *
|
||||
from mibuild.generic_platform import *
|
||||
from mibuild.xilinx import XilinxPlatform
|
||||
from mibuild.xilinx.vivado import XilinxVivadoToolchain
|
||||
from mibuild.xilinx.ise import XilinxISEToolchain
|
||||
|
||||
|
||||
"""
|
||||
This migen script produces proxy bitstreams to allow programming SPI flashes
|
||||
behind FPGAs. JTAG signalling is connected directly to SPI signalling. CS_N is
|
||||
asserted when the JTAG IR contains the USER1 instruction and the state is
|
||||
SHIFT-DR.
|
||||
|
||||
Xilinx bscan cells sample TDO on falling TCK and forward it.
|
||||
MISO requires sampling on rising CLK and leads to one cycle of latency.
|
||||
|
||||
https://github.com/m-labs/migen
|
||||
"""
|
||||
|
||||
|
||||
class Spartan3(Module):
|
||||
macro = "BSCAN_SPARTAN3"
|
||||
|
||||
def __init__(self, platform):
|
||||
self.clock_domains.cd_jtag = ClockDomain(reset_less=True)
|
||||
spi = platform.request("spiflash")
|
||||
shift = Signal()
|
||||
tdo = Signal()
|
||||
sel1 = Signal()
|
||||
self.comb += [
|
||||
self.cd_jtag.clk.eq(spi.clk),
|
||||
spi.cs_n.eq(~shift | ~sel1),
|
||||
]
|
||||
self.sync.jtag += tdo.eq(spi.miso)
|
||||
self.specials += Instance(self.macro,
|
||||
o_DRCK1=spi.clk, o_SHIFT=shift,
|
||||
o_TDI=spi.mosi, i_TDO1=tdo, i_TDO2=0,
|
||||
o_SEL1=sel1)
|
||||
|
||||
|
||||
class Spartan3A(Spartan3):
|
||||
macro = "BSCAN_SPARTAN3A"
|
||||
|
||||
|
||||
class Spartan6(Module):
|
||||
def __init__(self, platform):
|
||||
self.clock_domains.cd_jtag = ClockDomain(reset_less=True)
|
||||
spi = platform.request("spiflash")
|
||||
shift = Signal()
|
||||
tdo = Signal()
|
||||
sel = Signal()
|
||||
self.comb += self.cd_jtag.clk.eq(spi.clk), spi.cs_n.eq(~shift | ~sel)
|
||||
self.sync.jtag += tdo.eq(spi.miso)
|
||||
self.specials += Instance("BSCAN_SPARTAN6", p_JTAG_CHAIN=1,
|
||||
o_TCK=spi.clk, o_SHIFT=shift, o_SEL=sel,
|
||||
o_TDI=spi.mosi, i_TDO=tdo)
|
||||
|
||||
|
||||
class Series7(Module):
|
||||
def __init__(self, platform):
|
||||
self.clock_domains.cd_jtag = ClockDomain(reset_less=True)
|
||||
spi = platform.request("spiflash")
|
||||
clk = Signal()
|
||||
shift = Signal()
|
||||
tdo = Signal()
|
||||
sel = Signal()
|
||||
self.comb += self.cd_jtag.clk.eq(clk), spi.cs_n.eq(~shift | ~sel)
|
||||
self.sync.jtag += tdo.eq(spi.miso)
|
||||
self.specials += Instance("BSCANE2", p_JTAG_CHAIN=1,
|
||||
o_SHIFT=shift, o_TCK=clk, o_SEL=sel,
|
||||
o_TDI=spi.mosi, i_TDO=tdo)
|
||||
self.specials += Instance("STARTUPE2", i_CLK=0, i_GSR=0, i_GTS=0,
|
||||
i_KEYCLEARB=0, i_PACK=1, i_USRCCLKO=clk,
|
||||
i_USRCCLKTS=0, i_USRDONEO=1, i_USRDONETS=1)
|
||||
|
||||
|
||||
class XilinxBscanSpi(XilinxPlatform):
|
||||
pinouts = {
|
||||
# bitstreams are named by die, package does not matter, speed grade
|
||||
# should not matter.
|
||||
# cs_n, clk, mosi, miso, *pullups
|
||||
"xc3s100e": ("cp132",
|
||||
["M2", "N12", "N2", "N8"],
|
||||
"LVCMOS33", Spartan3),
|
||||
"xc3s1200e": ("fg320",
|
||||
["U3", "U16", "T4", "N10"],
|
||||
"LVCMOS33", Spartan3),
|
||||
"xc3s1400a": ("fg484",
|
||||
["Y4", "AA20", "AB14", "AB20"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s1400an": ("fgg484",
|
||||
["Y4", "AA20", "AB14", "AB20"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s1600e": ("fg320",
|
||||
["U3", "U16", "T4", "N10"],
|
||||
"LVCMOS33", Spartan3),
|
||||
"xc3s200a": ("fg320",
|
||||
["V3", "U16", "T11", "V16"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s200an": ("ftg256",
|
||||
["T2", "R14", "P10", "T14"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s250e": ("cp132",
|
||||
["M2", "N12", "N2", "N8"],
|
||||
"LVCMOS33", Spartan3),
|
||||
"xc3s400a": ("fg320",
|
||||
["V3", "U16", "T11", "V16"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s400an": ("fgg400",
|
||||
["Y2", "Y19", "W12", "W18"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s500e": ("cp132",
|
||||
["M2", "N12", "N2", "N8"],
|
||||
"LVCMOS33", Spartan3),
|
||||
"xc3s50a": ("ft256",
|
||||
["T2", "R14", "P10", "T14"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s50an": ("ftg256",
|
||||
["T2", "R14", "P10", "T14"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s700a": ("fg400",
|
||||
["Y2", "Y19", "W12", "W18"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3s700an": ("fgg484",
|
||||
["Y4", "AA20", "AB14", "AB20"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3sd1800a": ("cs484",
|
||||
["U7", "V17", "V13", "W17"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
"xc3sd3400a": ("cs484",
|
||||
["U7", "V17", "V13", "W17"],
|
||||
"LVCMOS33", Spartan3A),
|
||||
|
||||
"xc6slx100": ("csg484-2",
|
||||
["AB5", "W17", "AB17", "Y17", "V13", "W13"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx100t": ("csg484-2",
|
||||
["AB5", "W17", "AB17", "Y17", "V13", "W13"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx150": ("csg484-2",
|
||||
["AB5", "W17", "AB17", "Y17", "V13", "W13"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx150t": ("csg484-2",
|
||||
["AB5", "W17", "AB17", "Y17", "V13", "W13"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx16": ("cpg196-2",
|
||||
["P2", "N13", "P11", "N11", "N10", "P10"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx25": ("csg324-2",
|
||||
["V3", "R15", "T13", "R13", "T14", "V14"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx25t": ("csg324-2",
|
||||
["V3", "R15", "T13", "R13", "T14", "V14"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx45": ("csg324-2",
|
||||
["V3", "R15", "T13", "R13", "T14", "V14"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx45t": ("csg324-2",
|
||||
["V3", "R15", "T13", "R13", "T14", "V14"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx4": ("cpg196-2",
|
||||
["P2", "N13", "P11", "N11", "N10", "P10"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx4t": ("qg144-2",
|
||||
["P38", "P70", "P64", "P65", "P62", "P61"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx75": ("csg484-2",
|
||||
["AB5", "W17", "AB17", "Y17", "V13", "W13"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx75t": ("csg484-2",
|
||||
["AB5", "W17", "AB17", "Y17", "V13", "W13"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx9": ("cpg196-2",
|
||||
["P2", "N13", "P11", "N11", "N10", "P10"],
|
||||
"LVCMOS33", Spartan6),
|
||||
"xc6slx9t": ("qg144-2",
|
||||
["P38", "P70", "P64", "P65", "P62", "P61"],
|
||||
"LVCMOS33", Spartan6),
|
||||
|
||||
"xc7a100t": ("csg324-1",
|
||||
["L13", None, "K17", "K18", "L14", "M14"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7a15t": ("cpg236-1",
|
||||
["K19", None, "D18", "D19", "G18", "F18"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7a200t": ("fbg484-1",
|
||||
["T19", None, "P22", "R22", "P21", "R21"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7a35t": ("cpg236-1",
|
||||
["K19", None, "D18", "D19", "G18", "F18"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7a50t": ("cpg236-1",
|
||||
["K19", None, "D18", "D19", "G18", "F18"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7a75t": ("csg324-1",
|
||||
["L13", None, "K17", "K18", "L14", "M14"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7k160t": ("fbg484-1",
|
||||
["L16", None, "H18", "H19", "G18", "F19"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7k325t": ("fbg676-1",
|
||||
["C23", None, "B24", "A25", "B22", "A22"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7k355t": ("ffg901-1",
|
||||
["V26", None, "R30", "T30", "R28", "T28"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7k410t": ("fbg676-1",
|
||||
["C23", None, "B24", "A25", "B22", "A22"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7k420t": ("ffg1156-1",
|
||||
["V30", None, "AA33", "AA34", "Y33", "Y34"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7k480t": ("ffg1156-1",
|
||||
["V30", None, "AA33", "AA34", "Y33", "Y34"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7k70t": ("fbg484-1",
|
||||
["L16", None, "H18", "H19", "G18", "F19"],
|
||||
"LVCMOS25", Series7),
|
||||
"xc7v2000t": ("fhg1761-1",
|
||||
["AL36", None, "AM36", "AN36", "AJ36", "AJ37"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7v585t": ("ffg1157-1",
|
||||
["AL33", None, "AN33", "AN34", "AK34", "AL34"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vh580t": ("flg1155-1",
|
||||
["AL28", None, "AE28", "AF28", "AJ29", "AJ30"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vh870t": ("flg1932-1",
|
||||
["V32", None, "T33", "R33", "U31", "T31"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vx1140t": ("flg1926-1",
|
||||
["AK33", None, "AN34", "AN35", "AJ34", "AK34"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vx330t": ("ffg1157-1",
|
||||
["AL33", None, "AN33", "AN34", "AK34", "AL34"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vx415t": ("ffg1157-1",
|
||||
["AL33", None, "AN33", "AN34", "AK34", "AL34"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vx485t": ("ffg1157-1",
|
||||
["AL33", None, "AN33", "AN34", "AK34", "AL34"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vx550t": ("ffg1158-1",
|
||||
["C24", None, "A23", "A24", "B26", "A26"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vx690t": ("ffg1157-1",
|
||||
["AL33", None, "AN33", "AN34", "AK34", "AL34"],
|
||||
"LVCMOS18", Series7),
|
||||
"xc7vx980t": ("ffg1926-1",
|
||||
["AK33", None, "AN34", "AN35", "AJ34", "AK34"],
|
||||
"LVCMOS18", Series7),
|
||||
}
|
||||
|
||||
def __init__(self, device, pins, std):
|
||||
cs_n, clk, mosi, miso = pins[:4]
|
||||
io = ["spiflash", 0,
|
||||
Subsignal("cs_n", Pins(cs_n)),
|
||||
Subsignal("mosi", Pins(mosi)),
|
||||
Subsignal("miso", Pins(miso), Misc("PULLUP")),
|
||||
IOStandard(std),
|
||||
]
|
||||
if clk:
|
||||
io.append(Subsignal("clk", Pins(clk)))
|
||||
for i, p in enumerate(pins[4:]):
|
||||
io.append(Subsignal("pullup{}".format(i), Pins(p), Misc("PULLUP")))
|
||||
|
||||
XilinxPlatform.__init__(self, device, [io])
|
||||
if isinstance(self.toolchain, XilinxVivadoToolchain):
|
||||
self.toolchain.bitstream_commands.append(
|
||||
"set_property BITSTREAM.GENERAL.COMPRESS True [current_design]"
|
||||
)
|
||||
elif isinstance(self.toolchain, XilinxISEToolchain):
|
||||
self.toolchain.bitgen_opt += " -g compress"
|
||||
|
||||
@classmethod
|
||||
def make(cls, device, errors=False):
|
||||
pkg, pins, std, Top = cls.pinouts[device]
|
||||
platform = cls("{}-{}".format(device, pkg), pins, std)
|
||||
top = Top(platform)
|
||||
name = "bscan_spi_{}".format(device)
|
||||
dir = "build_{}".format(device)
|
||||
try:
|
||||
platform.build(top, build_name=name, build_dir=dir)
|
||||
except Exception as e:
|
||||
print("ERROR: build failed for {}: {}".format(device, e))
|
||||
if errors:
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import multiprocessing
|
||||
p = argparse.ArgumentParser(description="build bscan_spi bitstreams "
|
||||
"for openocd jtagspi flash driver")
|
||||
p.add_argument("device", nargs="*",
|
||||
default=sorted(list(XilinxBscanSpi.pinouts)),
|
||||
help="build for these devices (default: %(default)s)")
|
||||
p.add_argument("-p", "--parallel", default=1, type=int,
|
||||
help="number of parallel builds (default: %(default)s)")
|
||||
args = p.parse_args()
|
||||
pool = multiprocessing.Pool(args.parallel)
|
||||
pool.map(XilinxBscanSpi.make, args.device, chunksize=1)
|
|
@ -4802,6 +4802,49 @@ flash bank $_FLASHNAME cfi 0x00000000 0x02000000 2 4 $_TARGETNAME
|
|||
@c "cfi part_id" disabled
|
||||
@end deffn
|
||||
|
||||
@deffn {Flash Driver} jtagspi
|
||||
@cindex Generic JTAG2SPI driver
|
||||
@cindex SPI
|
||||
@cindex jtagspi
|
||||
@cindex bscan_spi
|
||||
Several FPGAs and CPLDs can retrieve their configuration (bitstream) from a
|
||||
SPI flash connected to them. To access this flash from the host, the device
|
||||
is first programmed with a special proxy bitstream that
|
||||
exposes the SPI flash on the device's JTAG interface. The flash can then be
|
||||
accessed through JTAG.
|
||||
|
||||
Since signaling between JTAG and SPI is compatible, all that is required for
|
||||
a proxy bitstream is to connect TDI-MOSI, TDO-MISO, TCK-CLK and activate
|
||||
the flash chip select when the JTAG state machine is in SHIFT-DR. Such
|
||||
a bitstream for several Xilinx FPGAs can be found in
|
||||
@file{contrib/loaders/flash/fpga/xilinx_bscan_spi.py}. It requires migen
|
||||
(@url{http://github.com/m-labs/migen}) and a Xilinx toolchain to build.
|
||||
|
||||
This flash bank driver requires a target on a JTAG tap and will access that
|
||||
tap directly. Since no support from the target is needed, the target can be a
|
||||
"testee" dummy. Since the target does not expose the flash memory
|
||||
mapping, target commands that would otherwise be expected to access the flash
|
||||
will not work. These include all @command{*_image} and
|
||||
@command{$target_name m*} commands as well as @command{program}. Equivalent
|
||||
functionality is available through the @command{flash write_bank},
|
||||
@command{flash read_bank}, and @command{flash verify_bank} commands.
|
||||
|
||||
@itemize
|
||||
@item @var{ir} ... is loaded into the JTAG IR to map the flash as the JTAG DR.
|
||||
For the bitstreams generated from @file{xilinx_bscan_spi.py} this is the
|
||||
@var{USER1} instruction.
|
||||
@item @var{dr_length} ... is the length of the DR register. This will be 1 for
|
||||
@file{xilinx_bscan_spi.py} bitstreams and most other cases.
|
||||
@end itemize
|
||||
|
||||
@example
|
||||
target create $_TARGETNAME testee -chain-position $_CHIPNAME.fpga
|
||||
set _XILINX_USER1 0x02
|
||||
set _DR_LENGTH 1
|
||||
flash bank $_FLASHNAME spi 0x0 0 0 0 $_TARGETNAME $_XILINX_USER1 $_DR_LENGTH
|
||||
@end example
|
||||
@end deffn
|
||||
|
||||
@deffn {Flash Driver} lpcspifi
|
||||
@cindex NXP SPI Flash Interface
|
||||
@cindex SPIFI
|
||||
|
|
|
@ -19,6 +19,7 @@ NOR_DRIVERS = \
|
|||
efm32.c \
|
||||
em357.c \
|
||||
faux.c \
|
||||
jtagspi.c \
|
||||
lpc2000.c \
|
||||
lpc288x.c \
|
||||
lpc2900.c \
|
||||
|
|
|
@ -59,6 +59,7 @@ extern struct flash_driver nrf51_flash;
|
|||
extern struct flash_driver mrvlqspi_flash;
|
||||
extern struct flash_driver psoc4_flash;
|
||||
extern struct flash_driver sim3x_flash;
|
||||
extern struct flash_driver jtagspi_flash;
|
||||
|
||||
/**
|
||||
* The list of built-in flash drivers.
|
||||
|
@ -102,6 +103,7 @@ static struct flash_driver *flash_drivers[] = {
|
|||
&mrvlqspi_flash,
|
||||
&psoc4_flash,
|
||||
&sim3x_flash,
|
||||
&jtagspi_flash,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,411 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2015 Robert Jordens <jordens@gmail.com> *
|
||||
* *
|
||||
* 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; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "imp.h"
|
||||
#include <jtag/jtag.h>
|
||||
#include <flash/nor/spi.h>
|
||||
#include <helper/time_support.h>
|
||||
|
||||
#define JTAGSPI_MAX_TIMEOUT 3000
|
||||
|
||||
|
||||
struct jtagspi_flash_bank {
|
||||
struct jtag_tap *tap;
|
||||
const struct flash_device *dev;
|
||||
int probed;
|
||||
uint32_t ir;
|
||||
uint32_t dr_len;
|
||||
};
|
||||
|
||||
FLASH_BANK_COMMAND_HANDLER(jtagspi_flash_bank_command)
|
||||
{
|
||||
struct jtagspi_flash_bank *info;
|
||||
|
||||
if (CMD_ARGC < 8)
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
|
||||
info = malloc(sizeof(struct jtagspi_flash_bank));
|
||||
if (info == NULL) {
|
||||
LOG_ERROR("no memory for flash bank info");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
bank->driver_priv = info;
|
||||
|
||||
info->tap = NULL;
|
||||
info->probed = 0;
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[6], info->ir);
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[7], info->dr_len);
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static void jtagspi_set_ir(struct flash_bank *bank)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
struct scan_field field;
|
||||
uint8_t buf[4];
|
||||
|
||||
if (buf_get_u32(info->tap->cur_instr, 0, info->tap->ir_length) == info->ir)
|
||||
return;
|
||||
|
||||
LOG_DEBUG("loading jtagspi ir");
|
||||
buf_set_u32(buf, 0, info->tap->ir_length, info->ir);
|
||||
field.num_bits = info->tap->ir_length;
|
||||
field.out_value = buf;
|
||||
field.in_value = NULL;
|
||||
jtag_add_ir_scan(info->tap, &field, TAP_IDLE);
|
||||
}
|
||||
|
||||
static void flip_u8(uint8_t *in, uint8_t *out, int len)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
out[i] = flip_u32(in[i], 8);
|
||||
}
|
||||
|
||||
static int jtagspi_cmd(struct flash_bank *bank, uint8_t cmd,
|
||||
uint32_t *addr, uint8_t *data, int len)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
struct scan_field fields[3];
|
||||
uint8_t cmd_buf[4];
|
||||
uint8_t *data_buf;
|
||||
int is_read, lenb, n;
|
||||
|
||||
/* LOG_DEBUG("cmd=0x%02x len=%i", cmd, len); */
|
||||
|
||||
n = 0;
|
||||
fields[n].num_bits = 8;
|
||||
cmd_buf[0] = cmd;
|
||||
if (addr) {
|
||||
h_u24_to_be(cmd_buf + 1, *addr);
|
||||
fields[n].num_bits += 24;
|
||||
}
|
||||
flip_u8(cmd_buf, cmd_buf, 4);
|
||||
fields[n].out_value = cmd_buf;
|
||||
fields[n].in_value = NULL;
|
||||
n++;
|
||||
|
||||
is_read = (len < 0);
|
||||
if (is_read)
|
||||
len = -len;
|
||||
lenb = DIV_ROUND_UP(len, 8);
|
||||
data_buf = malloc(lenb);
|
||||
if (lenb > 0) {
|
||||
if (data_buf == NULL) {
|
||||
LOG_ERROR("no memory for spi buffer");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
if (is_read) {
|
||||
fields[n].num_bits = info->dr_len;
|
||||
fields[n].out_value = NULL;
|
||||
fields[n].in_value = NULL;
|
||||
n++;
|
||||
fields[n].out_value = NULL;
|
||||
fields[n].in_value = data_buf;
|
||||
} else {
|
||||
flip_u8(data, data_buf, lenb);
|
||||
fields[n].out_value = data_buf;
|
||||
fields[n].in_value = NULL;
|
||||
}
|
||||
fields[n].num_bits = len;
|
||||
n++;
|
||||
}
|
||||
|
||||
jtagspi_set_ir(bank);
|
||||
jtag_add_dr_scan(info->tap, n, fields, TAP_IDLE);
|
||||
jtag_execute_queue();
|
||||
|
||||
if (is_read)
|
||||
flip_u8(data_buf, data, lenb);
|
||||
free(data_buf);
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static int jtagspi_probe(struct flash_bank *bank)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
struct flash_sector *sectors;
|
||||
uint8_t in_buf[3];
|
||||
uint32_t id;
|
||||
|
||||
if (info->probed)
|
||||
free(bank->sectors);
|
||||
info->probed = 0;
|
||||
|
||||
if (bank->target->tap == NULL) {
|
||||
LOG_ERROR("Target has no JTAG tap");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
info->tap = bank->target->tap;
|
||||
|
||||
jtagspi_cmd(bank, SPIFLASH_READ_ID, NULL, in_buf, -24);
|
||||
/* the table in spi.c has the manufacturer byte (first) as the lsb */
|
||||
id = le_to_h_u24(in_buf);
|
||||
|
||||
info->dev = NULL;
|
||||
for (const struct flash_device *p = flash_devices; p->name ; p++)
|
||||
if (p->device_id == id) {
|
||||
info->dev = p;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(info->dev)) {
|
||||
LOG_ERROR("Unknown flash device (ID 0x%08" PRIx32 ")", id);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
LOG_INFO("Found flash device \'%s\' (ID 0x%08" PRIx32 ")",
|
||||
info->dev->name, info->dev->device_id);
|
||||
|
||||
/* Set correct size value */
|
||||
bank->size = info->dev->size_in_bytes;
|
||||
|
||||
/* create and fill sectors array */
|
||||
bank->num_sectors =
|
||||
info->dev->size_in_bytes / info->dev->sectorsize;
|
||||
sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
|
||||
if (sectors == NULL) {
|
||||
LOG_ERROR("not enough memory");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
for (int sector = 0; sector < bank->num_sectors; sector++) {
|
||||
sectors[sector].offset = sector * info->dev->sectorsize;
|
||||
sectors[sector].size = info->dev->sectorsize;
|
||||
sectors[sector].is_erased = -1;
|
||||
sectors[sector].is_protected = 0;
|
||||
}
|
||||
|
||||
bank->sectors = sectors;
|
||||
info->probed = 1;
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static void jtagspi_read_status(struct flash_bank *bank, uint32_t *status)
|
||||
{
|
||||
uint8_t buf;
|
||||
jtagspi_cmd(bank, SPIFLASH_READ_STATUS, NULL, &buf, -8);
|
||||
*status = buf;
|
||||
/* LOG_DEBUG("status=0x%08" PRIx32, *status); */
|
||||
}
|
||||
|
||||
static int jtagspi_wait(struct flash_bank *bank, int timeout_ms)
|
||||
{
|
||||
uint32_t status;
|
||||
long long t0 = timeval_ms();
|
||||
long long dt;
|
||||
|
||||
do {
|
||||
dt = timeval_ms() - t0;
|
||||
jtagspi_read_status(bank, &status);
|
||||
if ((status & SPIFLASH_BSY_BIT) == 0) {
|
||||
LOG_DEBUG("waited %lld ms", dt);
|
||||
return ERROR_OK;
|
||||
}
|
||||
alive_sleep(1);
|
||||
} while (dt <= timeout_ms);
|
||||
|
||||
LOG_ERROR("timeout, device still busy");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
static int jtagspi_write_enable(struct flash_bank *bank)
|
||||
{
|
||||
uint32_t status;
|
||||
|
||||
jtagspi_cmd(bank, SPIFLASH_WRITE_ENABLE, NULL, NULL, 0);
|
||||
jtagspi_read_status(bank, &status);
|
||||
if ((status & SPIFLASH_WE_BIT) == 0) {
|
||||
LOG_ERROR("Cannot enable write to flash. Status=0x%08" PRIx32, status);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static int jtagspi_bulk_erase(struct flash_bank *bank)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
int retval;
|
||||
long long t0 = timeval_ms();
|
||||
|
||||
retval = jtagspi_write_enable(bank);
|
||||
if (retval != ERROR_OK)
|
||||
return retval;
|
||||
jtagspi_cmd(bank, info->dev->chip_erase_cmd, NULL, NULL, 0);
|
||||
retval = jtagspi_wait(bank, bank->num_sectors*JTAGSPI_MAX_TIMEOUT);
|
||||
LOG_INFO("took %lld ms", timeval_ms() - t0);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int jtagspi_sector_erase(struct flash_bank *bank, int sector)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
int retval;
|
||||
long long t0 = timeval_ms();
|
||||
|
||||
retval = jtagspi_write_enable(bank);
|
||||
if (retval != ERROR_OK)
|
||||
return retval;
|
||||
jtagspi_cmd(bank, info->dev->erase_cmd, &bank->sectors[sector].offset, NULL, 0);
|
||||
retval = jtagspi_wait(bank, JTAGSPI_MAX_TIMEOUT);
|
||||
LOG_INFO("sector %d took %lld ms", sector, timeval_ms() - t0);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int jtagspi_erase(struct flash_bank *bank, int first, int last)
|
||||
{
|
||||
int sector;
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
int retval;
|
||||
|
||||
LOG_DEBUG("erase from sector %d to sector %d", first, last);
|
||||
|
||||
if ((first < 0) || (last < first) || (last >= bank->num_sectors)) {
|
||||
LOG_ERROR("Flash sector invalid");
|
||||
return ERROR_FLASH_SECTOR_INVALID;
|
||||
}
|
||||
|
||||
if (!(info->probed)) {
|
||||
LOG_ERROR("Flash bank not probed");
|
||||
return ERROR_FLASH_BANK_NOT_PROBED;
|
||||
}
|
||||
|
||||
for (sector = first; sector <= last; sector++) {
|
||||
if (bank->sectors[sector].is_protected) {
|
||||
LOG_ERROR("Flash sector %d protected", sector);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (first == 0 && last == (bank->num_sectors - 1)
|
||||
&& info->dev->chip_erase_cmd != info->dev->erase_cmd) {
|
||||
LOG_DEBUG("Trying bulk erase.");
|
||||
retval = jtagspi_bulk_erase(bank);
|
||||
if (retval == ERROR_OK)
|
||||
return retval;
|
||||
else
|
||||
LOG_WARNING("Bulk flash erase failed. Falling back to sector erase.");
|
||||
}
|
||||
|
||||
for (sector = first; sector <= last; sector++) {
|
||||
retval = jtagspi_sector_erase(bank, sector);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("Sector erase failed.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int jtagspi_protect(struct flash_bank *bank, int set, int first, int last)
|
||||
{
|
||||
int sector;
|
||||
|
||||
if ((first < 0) || (last < first) || (last >= bank->num_sectors)) {
|
||||
LOG_ERROR("Flash sector invalid");
|
||||
return ERROR_FLASH_SECTOR_INVALID;
|
||||
}
|
||||
|
||||
for (sector = first; sector <= last; sector++)
|
||||
bank->sectors[sector].is_protected = set;
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static int jtagspi_protect_check(struct flash_bank *bank)
|
||||
{
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static int jtagspi_read(struct flash_bank *bank, uint8_t *buffer, uint32_t offset, uint32_t count)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
|
||||
if (!(info->probed)) {
|
||||
LOG_ERROR("Flash bank not yet probed.");
|
||||
return ERROR_FLASH_BANK_NOT_PROBED;
|
||||
}
|
||||
|
||||
jtagspi_cmd(bank, SPIFLASH_READ, &offset, buffer, -count*8);
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static int jtagspi_page_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = jtagspi_write_enable(bank);
|
||||
if (retval != ERROR_OK)
|
||||
return retval;
|
||||
jtagspi_cmd(bank, SPIFLASH_PAGE_PROGRAM, &offset, (uint8_t *) buffer, count*8);
|
||||
return jtagspi_wait(bank, JTAGSPI_MAX_TIMEOUT);
|
||||
}
|
||||
|
||||
static int jtagspi_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
int retval;
|
||||
uint32_t n;
|
||||
|
||||
if (!(info->probed)) {
|
||||
LOG_ERROR("Flash bank not yet probed.");
|
||||
return ERROR_FLASH_BANK_NOT_PROBED;
|
||||
}
|
||||
|
||||
for (n = 0; n < count; n += info->dev->pagesize) {
|
||||
retval = jtagspi_page_write(bank, buffer + n, offset + n,
|
||||
MIN(count - n, info->dev->pagesize));
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("page write error");
|
||||
return retval;
|
||||
}
|
||||
LOG_DEBUG("wrote page at 0x%08" PRIx32, offset + n);
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static int jtagspi_info(struct flash_bank *bank, char *buf, int buf_size)
|
||||
{
|
||||
struct jtagspi_flash_bank *info = bank->driver_priv;
|
||||
|
||||
if (!(info->probed)) {
|
||||
snprintf(buf, buf_size, "\nJTAGSPI flash bank not probed yet\n");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
snprintf(buf, buf_size, "\nSPIFI flash information:\n"
|
||||
" Device \'%s\' (ID 0x%08" PRIx32 ")\n",
|
||||
info->dev->name, info->dev->device_id);
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
struct flash_driver jtagspi_flash = {
|
||||
.name = "jtagspi",
|
||||
.flash_bank_command = jtagspi_flash_bank_command,
|
||||
.erase = jtagspi_erase,
|
||||
.protect = jtagspi_protect,
|
||||
.write = jtagspi_write,
|
||||
.read = jtagspi_read,
|
||||
.probe = jtagspi_probe,
|
||||
.auto_probe = jtagspi_probe,
|
||||
.erase_check = default_flash_blank_check,
|
||||
.protect_check = jtagspi_protect_check,
|
||||
.info = jtagspi_info
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
set _USER1 0x02
|
||||
|
||||
if { [info exists JTAGSPI_IR] } {
|
||||
set _JTAGSPI_IR $JTAGSPI_IR
|
||||
} else {
|
||||
set _JTAGSPI_IR $_USER1
|
||||
}
|
||||
|
||||
if { [info exists DR_LENGTH] } {
|
||||
set _DR_LENGTH $DR_LENGTH
|
||||
} else {
|
||||
set _DR_LENGTH 1
|
||||
}
|
||||
|
||||
if { [info exists TARGETNAME] } {
|
||||
set _TARGETNAME $TARGETNAME
|
||||
} else {
|
||||
set _TARGETNAME $_CHIPNAME.proxy
|
||||
}
|
||||
|
||||
if { [info exists FLASHNAME] } {
|
||||
set _FLASHNAME $FLASHNAME
|
||||
} else {
|
||||
set _FLASHNAME $_CHIPNAME.spi
|
||||
}
|
||||
|
||||
target create $_TARGETNAME testee -chain-position $_CHIPNAME.tap
|
||||
flash bank $_FLASHNAME jtagspi 0 0 0 0 $_TARGETNAME $_JTAGSPI_IR $_DR_LENGTH
|
||||
|
||||
proc jtagspi_init {chain_id proxy_bit} {
|
||||
# load proxy bitstream $proxy_bit and probe spi flash
|
||||
global _FLASHNAME
|
||||
pld load $chain_id $proxy_bit
|
||||
reset halt
|
||||
flash probe $_FLASHNAME
|
||||
}
|
||||
|
||||
proc jtagspi_program {bin addr} {
|
||||
# write and verify binary file $bin at offset $addr
|
||||
global _FLASHNAME
|
||||
flash write_image erase $bin $addr
|
||||
flash verify_bank $_FLASHNAME $bin $addr
|
||||
}
|
Loading…
Reference in New Issue