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
|
@c "cfi part_id" disabled
|
||||||
@end deffn
|
@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
|
@deffn {Flash Driver} lpcspifi
|
||||||
@cindex NXP SPI Flash Interface
|
@cindex NXP SPI Flash Interface
|
||||||
@cindex SPIFI
|
@cindex SPIFI
|
||||||
|
|
|
@ -19,6 +19,7 @@ NOR_DRIVERS = \
|
||||||
efm32.c \
|
efm32.c \
|
||||||
em357.c \
|
em357.c \
|
||||||
faux.c \
|
faux.c \
|
||||||
|
jtagspi.c \
|
||||||
lpc2000.c \
|
lpc2000.c \
|
||||||
lpc288x.c \
|
lpc288x.c \
|
||||||
lpc2900.c \
|
lpc2900.c \
|
||||||
|
|
|
@ -59,6 +59,7 @@ extern struct flash_driver nrf51_flash;
|
||||||
extern struct flash_driver mrvlqspi_flash;
|
extern struct flash_driver mrvlqspi_flash;
|
||||||
extern struct flash_driver psoc4_flash;
|
extern struct flash_driver psoc4_flash;
|
||||||
extern struct flash_driver sim3x_flash;
|
extern struct flash_driver sim3x_flash;
|
||||||
|
extern struct flash_driver jtagspi_flash;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of built-in flash drivers.
|
* The list of built-in flash drivers.
|
||||||
|
@ -102,6 +103,7 @@ static struct flash_driver *flash_drivers[] = {
|
||||||
&mrvlqspi_flash,
|
&mrvlqspi_flash,
|
||||||
&psoc4_flash,
|
&psoc4_flash,
|
||||||
&sim3x_flash,
|
&sim3x_flash,
|
||||||
|
&jtagspi_flash,
|
||||||
NULL,
|
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