forked from M-Labs/artiq
parent
91a4a7b0ee
commit
37a0d6580b
|
@ -0,0 +1,211 @@
|
||||||
|
"""
|
||||||
|
Driver for generic SPI on RTIO.
|
||||||
|
|
||||||
|
This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2).
|
||||||
|
|
||||||
|
Output event replacement is not supported and issuing commands at the same
|
||||||
|
time is an error.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from numpy import int64
|
||||||
|
from artiq.language.core import syscall, kernel, portable, now_mu, delay_mu
|
||||||
|
from artiq.language.types import TInt32, TNone
|
||||||
|
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"SPI_DATA_ADDR", "SPI_CONFIG_ADDR",
|
||||||
|
"SPI_OFFLINE", "SPI_END", "SPI_INPUT",
|
||||||
|
"SPI_CS_POLARITY", "SPI_CLK_POLARITY", "SPI_CLK_PHASE",
|
||||||
|
"SPI_LSB_FIRST", "SPI_HALF_DUPLEX",
|
||||||
|
"SPIMaster", "NRTSPIMaster"
|
||||||
|
]
|
||||||
|
|
||||||
|
SPI_DATA_ADDR = 0
|
||||||
|
SPI_CONFIG_ADDR = 1
|
||||||
|
|
||||||
|
SPI_OFFLINE = 0x01
|
||||||
|
SPI_END = 0x02
|
||||||
|
SPI_INPUT = 0x04
|
||||||
|
SPI_CS_POLARITY = 0x08
|
||||||
|
SPI_CLK_POLARITY = 0x10
|
||||||
|
SPI_CLK_PHASE = 0x20
|
||||||
|
SPI_LSB_FIRST = 0x40
|
||||||
|
SPI_HALF_DUPLEX = 0x80
|
||||||
|
|
||||||
|
|
||||||
|
class SPIMaster:
|
||||||
|
"""Core device Serial Peripheral Interface (SPI) bus master.
|
||||||
|
|
||||||
|
Owns one SPI bus.
|
||||||
|
|
||||||
|
This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2).
|
||||||
|
|
||||||
|
**Transfer Sequence**:
|
||||||
|
|
||||||
|
* If necessary, set the ``config`` register (:meth:`set_config` and
|
||||||
|
:meth:`set_config_mu`) to activate and configure the core and to set
|
||||||
|
various transfer parameters like transfer length, clock divider,
|
||||||
|
and chip selects.
|
||||||
|
* :meth:`write` to the ``data`` register. Writing starts the transfer.
|
||||||
|
* If the transfer included submitting the SPI input data as an RTIO input
|
||||||
|
event (``SPI_INPUT`` set), then :meth:`read` the ``data``.
|
||||||
|
* If ``SPI_END`` was not set, repeat the transfer sequence.
|
||||||
|
|
||||||
|
A **transaction** consists of one or more **transfers**. The chip select
|
||||||
|
pattern is asserted for the entire length of the transaction. All but the
|
||||||
|
last transfer are submitted with ``SPI_END`` cleared in the configuration
|
||||||
|
register.
|
||||||
|
|
||||||
|
:param channel: RTIO channel number of the SPI bus to control.
|
||||||
|
"""
|
||||||
|
kernel_invariants = {"core", "ref_period_mu", "channel"}
|
||||||
|
|
||||||
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
self.core = dmgr.get(core_device)
|
||||||
|
self.ref_period_mu = self.core.seconds_to_mu(
|
||||||
|
self.core.coarse_ref_period)
|
||||||
|
assert self.ref_period_mu == self.core.ref_multiplier
|
||||||
|
self.channel = channel
|
||||||
|
self.xfer_duration_mu = 2*self.ref_period_mu
|
||||||
|
|
||||||
|
@portable
|
||||||
|
def frequency_to_div(self, f):
|
||||||
|
"""Convert a SPI clock frequency to the closest SPI clock divider."""
|
||||||
|
return int64(round(
|
||||||
|
1/(f*self.core.mu_to_seconds(self.ref_period_mu))))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_config(self, flags, length, freq, c):
|
||||||
|
"""Set the configuration register.
|
||||||
|
|
||||||
|
* If ``SPI_CS_POLARITY`` is cleared (``cs`` active low, the default),
|
||||||
|
"``cs`` all deasserted" means "all ``cs_n`` bits high".
|
||||||
|
* ``cs_n`` is not mandatory in the pads supplied to the gateware core.
|
||||||
|
Framing and chip selection can also be handled independently
|
||||||
|
through other means, e.g. ``TTLOut``.
|
||||||
|
* If there is a ``miso`` wire in the pads supplied in the gateware,
|
||||||
|
input and output may be two signals ("4-wire SPI"),
|
||||||
|
otherwise ``mosi`` must be used for both output and input
|
||||||
|
("3-wire SPI") and ``SPI_HALF_DUPLEX`` must to be set
|
||||||
|
when reading data or when the slave drives the
|
||||||
|
``mosi`` signal at any point.
|
||||||
|
* The first bit output on ``mosi`` is always the MSB/LSB (depending
|
||||||
|
on ``SPI_LSB_FIRST``) of the ``data`` written, independent of
|
||||||
|
the ``length`` of the transfer. The last bit input from ``miso``
|
||||||
|
always ends up in the LSB/MSB (respectively) of the ``data`` read,
|
||||||
|
independent of the ``length`` of the transfer.
|
||||||
|
* ``cs`` is asserted at the beginning and deasserted at the end
|
||||||
|
of the transaction.
|
||||||
|
* ``cs`` handling is agnostic to whether it is one-hot or decoded
|
||||||
|
somewhere downstream. If it is decoded, "``cs`` all deasserted"
|
||||||
|
should be handled accordingly (no slave selected).
|
||||||
|
If it is one-hot, asserting multiple slaves should only be attempted
|
||||||
|
if ``miso`` is either not connected between slaves, or open
|
||||||
|
collector, or correctly multiplexed externally.
|
||||||
|
* Changes to the configuration register take effect on the start of the
|
||||||
|
next transfer with the exception of ``SPI_OFFLINE`` which takes
|
||||||
|
effect immediately.
|
||||||
|
* The SPI core can only be written to when it is idle or waiting
|
||||||
|
for the next transfer data. Writing (:meth:`set_config`,
|
||||||
|
:meth:`set_config_mu` or :meth:`write`)
|
||||||
|
when the core is busy will result in an RTIO busy error being logged.
|
||||||
|
|
||||||
|
This method advances the timeline by one coarse RTIO clock cycle.
|
||||||
|
|
||||||
|
**Configuration flags**:
|
||||||
|
|
||||||
|
* :const:`SPI_OFFLINE`: all pins high-z (reset=1)
|
||||||
|
* :const:`SPI_END`: transfer in progress (reset=1)
|
||||||
|
* :const:`SPI_INPUT`: submit SPI read data as RTIO input event when
|
||||||
|
transfer is complete (reset=0)
|
||||||
|
* :const:`SPI_CS_POLARITY`: active level of ``cs_n`` (reset=0)
|
||||||
|
* :const:`SPI_CLK_POLARITY`: idle level of ``clk`` (reset=0)
|
||||||
|
* :const:`SPI_CLK_PHASE`: first edge after ``cs`` assertion to sample
|
||||||
|
data on (reset=0). In Motorola/Freescale SPI language
|
||||||
|
(:const:`SPI_CLK_POLARITY`, :const:`SPI_CLK_PHASE`) == (CPOL, CPHA):
|
||||||
|
|
||||||
|
- (0, 0): idle low, output on falling, input on rising
|
||||||
|
- (0, 1): idle low, output on rising, input on falling
|
||||||
|
- (1, 0): idle high, output on rising, input on falling
|
||||||
|
- (1, 1): idle high, output on falling, input on rising
|
||||||
|
* :const:`SPI_LSB_FIRST`: LSB is the first bit on the wire (reset=0)
|
||||||
|
* :const:`SPI_HALF_DUPLEX`: 3-wire SPI, in/out on ``mosi`` (reset=0)
|
||||||
|
|
||||||
|
:param flags: A bit map of `SPI_*` flags.
|
||||||
|
:param length: Number of bits to write during the next transfer.
|
||||||
|
(reset=1)
|
||||||
|
:param freq: Desired SPI clock frequency. (reset=f_rtio/2)
|
||||||
|
:param cs: Bit pattern of chip selects to assert.
|
||||||
|
Or number of the chip select to assert if ``cs`` is decoded
|
||||||
|
downstream. (reset=0)
|
||||||
|
"""
|
||||||
|
self.set_config_mu(
|
||||||
|
flags, length, self.frequency_to_div(write_freq), cs)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_config_mu(self, flags, length, div, cs):
|
||||||
|
"""Set the ``config`` register (in SPI bus machine units).
|
||||||
|
|
||||||
|
.. seealso:: :meth:`set_config`
|
||||||
|
|
||||||
|
:param flags: A bit map of `SPI_*` flags.
|
||||||
|
:param length: Number of bits to write during the next transfer.
|
||||||
|
(reset=1)
|
||||||
|
:param div: Counter load value to divide the RTIO
|
||||||
|
clock by to generate the SPI clock. (minimum=2, reset=2)
|
||||||
|
``f_rtio_clk/f_spi == div``. If ``div`` is odd,
|
||||||
|
the setup phase of the SPI clock is one coarse RTIO clock cycle
|
||||||
|
longer than the hold phase.
|
||||||
|
:param cs: Bit pattern of chip selects to assert.
|
||||||
|
Or number of the chip select to assert if ``cs`` is decoded
|
||||||
|
downstream. (reset=0)
|
||||||
|
"""
|
||||||
|
if length > 32 or length < 1:
|
||||||
|
raise ValueError("Invalid SPI transfer length")
|
||||||
|
if div > 257 or div < 2:
|
||||||
|
raise ValueError("Invalid SPI clock divider")
|
||||||
|
self.xfer_duration_mu = (length + 1)*div*self.ref_period_mu
|
||||||
|
rtio_output(now_mu(), self.channel, SPI_CONFIG_ADDR, flags |
|
||||||
|
((length - 1) << 8) | ((div - 2) << 16) | (cs << 24))
|
||||||
|
delay_mu(self.ref_period_mu)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def write(self, data):
|
||||||
|
"""Write SPI data to shift register register and start transfer.
|
||||||
|
|
||||||
|
* The ``data`` register and the shift register are 32 bits wide.
|
||||||
|
* Data writes take one ``ref_period`` cycle.
|
||||||
|
* A transaction consisting of a single transfer (``SPI_END``) takes
|
||||||
|
:attr:`xfer_duration_mu` ``=(n + 1)*div`` cycles RTIO time where
|
||||||
|
``n`` is the number of bits and ``div`` is the SPI clock divider.
|
||||||
|
* Transfers in a multi-transfer transaction take up to one SPI clock
|
||||||
|
cycle less time depending on multiple parameters. Advanced users may
|
||||||
|
rewind the timeline appropriately to achieve faster multi-transfer
|
||||||
|
transactions.
|
||||||
|
* The SPI core will be busy for the duration of the SPI transfer.
|
||||||
|
* For bit alignment and bit ordering see :meth:`set_config`.
|
||||||
|
* The SPI core can only be written to when it is idle or waiting
|
||||||
|
for the next transfer data. Writing (:meth:`set_config`,
|
||||||
|
:meth:`set_config_mu` or :meth:`write`)
|
||||||
|
when the core is busy will result in an RTIO busy error being logged.
|
||||||
|
|
||||||
|
This method advances the timeline by the duration of one
|
||||||
|
single-transfer SPI transaction (:attr:`xfer_duration_mu`).
|
||||||
|
|
||||||
|
:param data: SPI output data to be written.
|
||||||
|
"""
|
||||||
|
rtio_output(now_mu(), self.channel, SPI_DATA_ADDR, data)
|
||||||
|
delay_mu(self.xfer_duration_mu)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def read(self):
|
||||||
|
"""Read SPI data submitted by the SPI core.
|
||||||
|
|
||||||
|
For bit alignment and bit ordering see :meth:`set_config`.
|
||||||
|
|
||||||
|
This method does not alter the timeline.
|
||||||
|
|
||||||
|
:return: SPI input data.
|
||||||
|
"""
|
||||||
|
return rtio_input_data(self.channel)
|
|
@ -0,0 +1,116 @@
|
||||||
|
from migen import *
|
||||||
|
|
||||||
|
from misoc.cores.spi2 import SPIMachine, SPIInterfaceXC7Diff, SPIInterface
|
||||||
|
from artiq.gateware.rtio import rtlink
|
||||||
|
|
||||||
|
|
||||||
|
class SPIMaster(Module):
|
||||||
|
"""
|
||||||
|
RTIO SPI Master version 2.
|
||||||
|
|
||||||
|
Register address and bit map:
|
||||||
|
|
||||||
|
data (address 0):
|
||||||
|
32 write/read data
|
||||||
|
|
||||||
|
config (address 1):
|
||||||
|
1 offline: all pins high-z (reset=1)
|
||||||
|
1 end: end transaction with next transfer (reset=1)
|
||||||
|
1 input: submit read data on RTIO input when readable (reset=0)
|
||||||
|
1 cs_polarity: active level of chip select (reset=0)
|
||||||
|
1 clk_polarity: idle level of clk (reset=0)
|
||||||
|
1 clk_phase: first edge after cs assertion to sample data on (reset=0)
|
||||||
|
(clk_polarity, clk_phase) == (CPOL, CPHA) in Freescale language.
|
||||||
|
(0, 0): idle low, output on falling, input on rising
|
||||||
|
(0, 1): idle low, output on rising, input on falling
|
||||||
|
(1, 0): idle high, output on rising, input on falling
|
||||||
|
(1, 1): idle high, output on falling, input on rising
|
||||||
|
There is never a clk edge during a cs edge.
|
||||||
|
1 lsb_first: LSB is the first bit on the wire (reset=0)
|
||||||
|
1 half_duplex: 3-wire SPI, in/out on mosi (reset=0)
|
||||||
|
5 length: 1-32 bits = length + 1 (reset=0)
|
||||||
|
3 padding
|
||||||
|
8 div: counter load value to divide this module's clock
|
||||||
|
to generate the SPI write clk (reset=0)
|
||||||
|
f_clk/f_spi == div + 2
|
||||||
|
8 cs: active high bit pattern of chip selects (reset=0)
|
||||||
|
"""
|
||||||
|
def __init__(self, pads, pads_n=None):
|
||||||
|
to_rio_phy = ClockDomainsRenamer("rio_phy")
|
||||||
|
if pads_n is None:
|
||||||
|
interface = SPIInterface(pads)
|
||||||
|
else:
|
||||||
|
interface = SPIInterfaceXC7Diff(pads, pads_n)
|
||||||
|
interface = to_rio_phy(interface)
|
||||||
|
spi = to_rio_phy(SPIMachine(data_width=32, div_width=8))
|
||||||
|
self.submodules += interface, spi
|
||||||
|
|
||||||
|
self.rtlink = rtlink.Interface(
|
||||||
|
rtlink.OInterface(len(spi.reg.pdo), address_width=1,
|
||||||
|
enable_replace=False),
|
||||||
|
rtlink.IInterface(len(spi.reg.pdi), timestamped=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
config = Record([
|
||||||
|
("offline", 1),
|
||||||
|
("end", 1),
|
||||||
|
("input", 1),
|
||||||
|
("cs_polarity", 1),
|
||||||
|
("clk_polarity", 1),
|
||||||
|
("clk_phase", 1),
|
||||||
|
("lsb_first", 1),
|
||||||
|
("half_duplex", 1),
|
||||||
|
("length", 5),
|
||||||
|
("padding", 3),
|
||||||
|
("div", 8),
|
||||||
|
("cs", 8),
|
||||||
|
])
|
||||||
|
assert len(config) == len(spi.reg.pdo) == len(spi.reg.pdi) == 32
|
||||||
|
|
||||||
|
config.offline.reset = 1
|
||||||
|
config.end.reset = 1
|
||||||
|
read = Signal()
|
||||||
|
|
||||||
|
self.sync.rio += [
|
||||||
|
If(self.rtlink.i.stb,
|
||||||
|
read.eq(0)
|
||||||
|
),
|
||||||
|
If(self.rtlink.o.stb & spi.writable,
|
||||||
|
If(self.rtlink.o.address,
|
||||||
|
config.raw_bits().eq(self.rtlink.o.data)
|
||||||
|
).Else(
|
||||||
|
read.eq(config.input)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.comb += [
|
||||||
|
spi.length.eq(config.length),
|
||||||
|
spi.end.eq(config.end),
|
||||||
|
spi.cg.div.eq(config.div),
|
||||||
|
spi.clk_phase.eq(config.clk_phase),
|
||||||
|
spi.reg.lsb_first.eq(config.lsb_first),
|
||||||
|
|
||||||
|
interface.half_duplex.eq(config.half_duplex),
|
||||||
|
interface.cs.eq(config.cs),
|
||||||
|
interface.cs_polarity.eq(Replicate(
|
||||||
|
config.cs_polarity, len(interface.cs_polarity))),
|
||||||
|
interface.clk_polarity.eq(config.clk_polarity),
|
||||||
|
interface.offline.eq(config.offline),
|
||||||
|
interface.cs_next.eq(spi.cs_next),
|
||||||
|
interface.clk_next.eq(spi.clk_next),
|
||||||
|
interface.ce.eq(spi.ce),
|
||||||
|
interface.sample.eq(spi.reg.sample),
|
||||||
|
spi.reg.sdi.eq(interface.sdi),
|
||||||
|
interface.sdo.eq(spi.reg.sdo),
|
||||||
|
|
||||||
|
spi.load.eq(self.rtlink.o.stb & spi.writable &
|
||||||
|
~self.rtlink.o.address),
|
||||||
|
spi.reg.pdo.eq(self.rtlink.o.data),
|
||||||
|
self.rtlink.o.busy.eq(~spi.writable),
|
||||||
|
self.rtlink.i.stb.eq(spi.readable & read),
|
||||||
|
self.rtlink.i.data.eq(spi.reg.pdi)
|
||||||
|
]
|
||||||
|
self.probes = []
|
|
@ -15,7 +15,7 @@ requirements:
|
||||||
- python >=3.5.3,<3.6
|
- python >=3.5.3,<3.6
|
||||||
- setuptools 33.1.1
|
- setuptools 33.1.1
|
||||||
- migen 0.7 py35_4+git9c3a301
|
- migen 0.7 py35_4+git9c3a301
|
||||||
- misoc 0.9 py35_10+git3072d794
|
- misoc 0.9 py35_11+git10062189
|
||||||
- jesd204b 0.4
|
- jesd204b 0.4
|
||||||
- microscope
|
- microscope
|
||||||
- binutils-or1k-linux >=2.27
|
- binutils-or1k-linux >=2.27
|
||||||
|
|
|
@ -33,6 +33,12 @@ These drivers are for the core device and the peripherals closely integrated int
|
||||||
.. automodule:: artiq.coredevice.spi
|
.. automodule:: artiq.coredevice.spi
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
:mod:`artiq.coredevice.spi2` module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: artiq.coredevice.spi2
|
||||||
|
:members:
|
||||||
|
|
||||||
:mod:`artiq.coredevice.ad5360` module
|
:mod:`artiq.coredevice.ad5360` module
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue