diff --git a/litex/soc/cores/i2c/__init__.py b/litex/soc/cores/i2c/__init__.py new file mode 100644 index 0000000000..116ac3dbba --- /dev/null +++ b/litex/soc/cores/i2c/__init__.py @@ -0,0 +1,91 @@ +# +# This file is part of LiteI2C +# +# Copyright (c) 2020 Antmicro +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * + +from litex.soc.integration.doc import AutoDoc +from litex.soc.interconnect import wishbone, stream +from litex.soc.interconnect.csr import * + +from litex.soc.cores.i2c.common import * +from litex.soc.cores.i2c.crossbar import LiteI2CCrossbar +from litex.soc.cores.i2c.master import LiteI2CMaster +from litex.soc.cores.i2c.generic_sdr import LiteI2CPHYCore + + +class LiteI2CCore(Module): + def __init__(self): + self.source = stream.Endpoint(i2c_core2phy_layout) + self.sink = stream.Endpoint(i2c_phy2core_layout) + self.enable = Signal() + + +class LiteI2C(Module, AutoCSR, AutoDoc): + """I2C Controller wrapper. + + The ``LiteI2C`` class provides a wrapper that can instantiate both ``LiteI2CMMAP`` and ``LiteI2CMaster`` and connect them to the PHY. + + Both options can be used at the same time with help of ``mux_sel`` register which allows to share access to PHY via crossbar. + + Parameters + ---------- + phy : Module + Module or object that contains PHY stream interfaces and a enable signal to connect the ``LiteI2CCore`` to. + + clk_freq : int + Frequency of a clock connected to LiteI2C. + + clock_domain : str + Name of LiteI2C clock domain. + + with_mmap : bool + Enables memory-mapped I2C flash controller. + + with_master : bool + Enables register-operated I2C master controller. + + mmap_endianness : string + If endianness is set to ``small`` then byte order of each 32-bit word comming MMAP core will be reversed. + + Attributes + ---------- + bus : Interface(), out + Wishbone interface for memory-mapped flash access. + """ + + def __init__(self, sys_clk_freq, phy=None, pads=None, clock_domain="sys", + with_master=True, master_tx_fifo_depth=1, master_rx_fifo_depth=1): + + if phy is None: + if pads is None: + raise ValueError("Either phy or pads must be provided.") + self.submodules.phy = phy = LiteI2CPHYCore(pads, clock_domain, sys_clk_freq) + + + self.submodules.crossbar = crossbar = LiteI2CCrossbar(clock_domain) + + self.comb += phy.enable.eq(crossbar.enable) + + if with_master: + self.submodules.master = master = LiteI2CMaster( + tx_fifo_depth = master_tx_fifo_depth, + rx_fifo_depth = master_rx_fifo_depth) + port_master = crossbar.get_port(master.enable) + self.comb += [ + port_master.source.connect(master.sink), + master.source.connect(port_master.sink), + ] + + if clock_domain != "sys": + self.comb += [ + crossbar.tx_cdc.source.connect(phy.sink), + phy.source.connect(crossbar.rx_cdc.sink), + ] + else: + self.comb += [ + crossbar.master.source.connect(phy.sink), + phy.source.connect(crossbar.master.sink), + ] diff --git a/litex/soc/cores/i2c/clkgen.py b/litex/soc/cores/i2c/clkgen.py new file mode 100644 index 0000000000..7a249a60b6 --- /dev/null +++ b/litex/soc/cores/i2c/clkgen.py @@ -0,0 +1,127 @@ +# +# This file is part of Litei2C +# +# Copyright (c) 2020 Antmicro +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * + +import math + +from litex.soc.integration.doc import AutoDoc, ModuleDoc + +from litex.build.io import SDRTristate + + +def freq_to_div(sys_clk_freq, freq): + return math.ceil(sys_clk_freq / (4*freq)) - 1 + +class LiteI2CClkGen(Module, AutoDoc): + """i2C Clock generator + + The ``Litei2CClkGen`` class provides a generic i2C clock generator. + + Parameters + ---------- + pads : Object + i2C pads description. + + device : str + Device type for determining how to get output pin if it was not provided in pads. + + cnt_width : int + Width of the internal counter ``cnt`` used for dividing the clock. + + with_ddr : bool + Generate additional ``sample`` and ``update`` signals. + + Attributes + ---------- + div : Signal(8), in + Clock divisor, output clock frequency will be equal to ``sys_clk_freq/(4*(1+div))``. + + posedge : Signal(), out + Outputs 1 when there is a rising edge on the generated clock, 0 otherwise. + + negedge : Signal(), out + Outputs 1 when there is a falling edge on the generated clock, 0 otherwise. + + en : Signal(), in + Clock enable input, output clock will be generated if set to 1, 0 resets the core. + + sample : Signal(), out + Outputs 1 when ``sample_cnt==cnt``, can be used to sample incoming DDR data. + + sample_cnt : Signal(8), in + Controls generation of the ``sample`` signal. + + update : Signal(), out + Outputs 1 when ``update_cnt==cnt``, can be used to update outgoing DDR data. + + update_cnt : Signal(8), in + Controls generation of the ``update`` signal. + """ + def __init__(self, pads, i2c_speed_mode, sys_clk_freq): + self.posedge = posedge = Signal() + self.negedge = negedge = Signal() + self.tx = tx = Signal() + self.rx = rx = Signal() + self.en = en = Signal() + self.keep_low = keep_low = Signal() + + cnt_width = bits_for(freq_to_div(sys_clk_freq, 100000)) + + div = Signal(cnt_width) + cnt = Signal(cnt_width) + sub_cnt = Signal(2) + clk = Signal(reset=1) + + self.comb += [ + Case(i2c_speed_mode, { + 0 : div.eq(freq_to_div(sys_clk_freq, 100000)), # 100kHz (Standard Mode) + 1 : div.eq(freq_to_div(sys_clk_freq, 400000)), # 400kHz (Fast Mode) + 2 : div.eq(freq_to_div(sys_clk_freq, 1000000)), # 1MHz (Fast Mode Plus) + })] + + self.comb += [ + posedge.eq(en & (sub_cnt == 2) & (cnt == div)), + negedge.eq(en & (sub_cnt == 0) & (cnt == div)), + tx.eq(en & (sub_cnt == 1) & (cnt == div)), + rx.eq(en & (sub_cnt == 3) & (cnt == div)), + ] + + # Delayed edge to account for IO register delays. + self.posedge_reg = posedge_reg = Signal() + self.posedge_reg2 = posedge_reg2 = Signal() + + self.sync += [ + posedge_reg.eq(posedge), + posedge_reg2.eq(posedge_reg), + ] + + self.sync += [ + If(en, + If(cnt < div, + cnt.eq(cnt+1), + ).Else( + cnt.eq(0), + clk.eq(sub_cnt[1]), + If(sub_cnt < 3, + sub_cnt.eq(sub_cnt+1), + ).Else( + sub_cnt.eq(0), + ) + ) + ).Else( + clk.eq(~keep_low), + cnt.eq(0), + sub_cnt.eq(0), + ) + ] + + self.specials += SDRTristate(pads.scl, + o = Signal(), # I2C uses Pull-ups, only drive low. + oe = ~clk, # Drive when scl is low. + i = Signal(), # Not used. + ) + diff --git a/litex/soc/cores/i2c/common.py b/litex/soc/cores/i2c/common.py new file mode 100644 index 0000000000..fd28cfee1b --- /dev/null +++ b/litex/soc/cores/i2c/common.py @@ -0,0 +1,46 @@ +# +# This file is part of Litei2c +# +# Copyright (c) 2020 Antmicro +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * +from migen.genlib.cdc import MultiReg + +# Core <-> PHY Layouts ----------------------------------------------------------------------------- + +""" +Stream layout for LiteI2CCore->PHY connection: +data - flash byte address when cmd=1, data to transmit when cmd=2, unused in cmd=0 +len - xfer length (in bits) +width - xfer width (1/2/4/8) +mask - dq output enable control (1 enables a output on a particular pin) +""" +i2c_core2phy_layout = [ + ("data", 32), + ("addr", 7), + ("len_tx", 3), + ("len_rx", 3) +] +""" +Stream layout for PHY->LiteI2CCore connection +data - 32-bits of data from flash +""" +i2c_phy2core_layout = [ + ("data", 32), + ("nack", 1), + ("unfinished_tx", 1), + ("unfinished_rx", 1) +] + +MMAP_DEFAULT_TIMEOUT = 256 + + +# Helpers ------------------------------------------------------------------------------------------ + +class ResyncReg(Module): + def __init__(self, src, dst, clock_domain): + if clock_domain == "sys": + self.comb += dst.eq(src) + else: + self.specials += MultiReg(src, dst, clock_domain) diff --git a/litex/soc/cores/i2c/crossbar.py b/litex/soc/cores/i2c/crossbar.py new file mode 100644 index 0000000000..313bc5dcc7 --- /dev/null +++ b/litex/soc/cores/i2c/crossbar.py @@ -0,0 +1,95 @@ +# +# This file is part of LiteI2C +# +# Copyright (c) 2015 Florent Kermarrec +# Copyright (c) 2020 Antmicro +# SPDX-License-Identifier: BSD-2-Clause + +from collections import OrderedDict + +from migen import * +from migen.genlib.roundrobin import RoundRobin +from litex.soc.cores.i2c.common import * + +from litex.soc.interconnect import stream + + +class LiteI2CMasterPort: + def __init__(self): + self.source = stream.Endpoint(i2c_core2phy_layout) + self.sink = stream.Endpoint(i2c_phy2core_layout) + + +class LiteI2CSlavePort: + def __init__(self): + self.source = stream.Endpoint(i2c_phy2core_layout) + self.sink = stream.Endpoint(i2c_core2phy_layout) + + +class LiteI2CCrossbar(Module): + def __init__(self, cd): + self.cd = cd + self.users = [] + self.master = LiteI2CMasterPort() + if cd != "sys": + rx_cdc = stream.AsyncFIFO(i2c_phy2core_layout, 32, buffered=True) + tx_cdc = stream.AsyncFIFO(i2c_core2phy_layout, 32, buffered=True) + self.submodules.rx_cdc = ClockDomainsRenamer({"write": cd, "read": "sys"})(rx_cdc) + self.submodules.tx_cdc = ClockDomainsRenamer({"write": "sys", "read": cd})(tx_cdc) + self.comb += [ + self.rx_cdc.source.connect(self.master.sink), + self.master.source.connect(self.tx_cdc.sink), + ] + + self.enable = Signal() + self.user_enable = [] + self.user_request = [] + + def get_port(self, enable, request = None): + user_port = LiteI2CSlavePort() + internal_port = LiteI2CSlavePort() + + tx_stream = user_port.sink + + self.comb += tx_stream.connect(internal_port.sink) + + rx_stream = internal_port.source + + self.comb += rx_stream.connect(user_port.source) + + if request is None: + request = Signal() + self.comb += request.eq(enable) + + self.users.append(internal_port) + self.user_enable.append(self.enable.eq(enable)) + self.user_request.append(request) + + return user_port + + def do_finalize(self): + self.submodules.rr = RoundRobin(len(self.users)) + + # TX + self.submodules.tx_mux = tx_mux = stream.Multiplexer(i2c_core2phy_layout, len(self.users)) + + # RX + self.submodules.rx_demux = rx_demux = stream.Demultiplexer(i2c_phy2core_layout, len(self.users)) + + for i, user in enumerate(self.users): + self.comb += [ + user.sink.connect(getattr(tx_mux, f"sink{i}")), + getattr(rx_demux, f"source{i}").connect(user.source), + ] + + self.comb += [ + self.rr.request.eq(Cat(self.user_request)), + + self.tx_mux.source.connect(self.master.source), + self.tx_mux.sel.eq(self.rr.grant), + + self.master.sink.connect(self.rx_demux.sink), + self.rx_demux.sel.eq(self.rr.grant), + + Case(self.rr.grant, dict(enumerate(self.user_enable))), + ] diff --git a/litex/soc/cores/i2c/generic_sdr.py b/litex/soc/cores/i2c/generic_sdr.py new file mode 100644 index 0000000000..1aa1dea4f7 --- /dev/null +++ b/litex/soc/cores/i2c/generic_sdr.py @@ -0,0 +1,466 @@ +# +# This file is part of LiteI2C +# +# Copyright (c) 2020-2021 Antmicro +# Copyright (c) 2021 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * + +from litex.gen.genlib.misc import WaitTimer + +from litex.soc.cores.i2c.common import * +from litex.soc.cores.i2c.clkgen import LiteI2CClkGen + +from litex.soc.interconnect import stream +from litex.soc.interconnect.csr import * + +from litex.build.io import SDRTristate + +from litex.soc.integration.doc import AutoDoc + +# LiteI2C PHY Core --------------------------------------------------------------------------------- + +class LiteI2CPHYCore(Module, AutoCSR, AutoDoc): + """LiteI2C PHY instantiator + + The ``LiteI2CPHYCore`` class provides a generic PHY that can be connected to the ``LiteI2CCore``. + + It supports single/dual/quad/octal output reads from the flash chips. + + The following diagram shows how each clock configuration option relates to outputs and input sampling in DDR mode: + + .. wavedrom:: ../../doc/ddr-timing-diagram.json + + Parameters + ---------- + pads : Object + I2C pads description. + + flash : SpiNorFlashModule + SpiNorFlashModule configuration object. + + device : str + Device type for use by the ``LiteI2CClkGen``. + + default_divisor : int + Default frequency divisor for clkgen. + + Attributes + ---------- + source : Endpoint(i2c_phy2core_layout), out + Data stream. + + sink : Endpoint(i2c_core2phy_layout), in + Control stream. + + cs : Signal(), in + Flash CS signal. + + speed_mode : CSRStorage + Register which holds a clock divisor value applied to clkgen. + """ + def __init__(self, pads, clock_domain, sys_clk_freq): + self.source = source = stream.Endpoint(i2c_phy2core_layout) + self.sink = sink = stream.Endpoint(i2c_core2phy_layout) + self.enable = enable = Signal() + self._i2c_speed_mode = i2c_speed_mode = Signal(8) + + self.speed_mode = speed_mode = CSRStorage(8, reset=0) + + # # # + + # Resynchronize CSR Clk Divisor to LiteI2C Clk Domain. + self.submodules += ResyncReg(speed_mode.storage, i2c_speed_mode, clock_domain) + + # Clock Generator. + self.submodules.clkgen = clkgen = LiteI2CClkGen(pads, i2c_speed_mode, sys_clk_freq) + + error = Signal(reset_less=True) + + # SDA + sda_o = Signal() + sda_i = Signal() + sda_oe = Signal() + self.specials += SDRTristate( + io = pads.sda, + o = Signal(), # I2C uses Pull-ups, only drive low. + oe = sda_oe & ~sda_o, # Drive when oe and sda is low. + i = sda_i, + ) + + bytes_send = Signal(3, reset_less=True) + bytes_recv = Signal(3, reset_less=True) + + tx_done = Signal(reset_less=True) + + unfinished_tx = Signal(reset_less=True) + unfinished_rx = Signal(reset_less=True) + + # Data Shift Registers. + + sr_addr = Signal(7, reset_less=True) + sr_addr_cnt = Signal(3, reset_less=True) + + sr_cnt = Signal(8, reset_less=True) + sr_out_load = Signal() + sr_out_shift = Signal() + sr_out = Signal(len(sink.data), reset_less=True) + sr_out_en = Signal() + sr_in_shift = Signal() + sr_in = Signal(len(sink.data), reset_less=True) + + # Data Out Generation/Load/Shift. + self.comb += [ + If(sr_out_en, + sda_oe.eq(1), + sda_o.eq(sr_out[-1:]), + ) + ] + self.sync += If(sr_out_load, + sr_out.eq(sink.data << (len(sink.data) - sink.len_tx * 8)), + ) + self.sync += If(sr_out_shift, sr_out.eq(Cat(Signal(1), sr_out))) + + # Data In Shift. + self.sync += If(sr_in_shift, sr_in.eq(Cat(sda_i, sr_in))) + + # FSM. + self.submodules.fsm = fsm = FSM(reset_state="WAIT-CMD-DATA") + fsm.act("WAIT-CMD-DATA", + NextValue(error, 0), + NextValue(tx_done, 0), + NextValue(unfinished_tx, 0), + NextValue(unfinished_rx, 0), + # Wait for CS and a CMD from the Core. + If(enable & sink.valid, + # Start XFER. + NextState("START"), + ), + ) + + fsm.act("START", + # Generate Clk. + clkgen.en.eq(1), + sda_oe.eq(1), + sda_o.eq(0), + NextValue(sr_addr, sink.addr), + NextValue(sr_addr_cnt, 0), + If(clkgen.negedge, + NextState("SEND_ADDR"), + ), + ) + + + fsm.act("SEND_ADDR", + # Generate Clk. + clkgen.en.eq(1), + sda_oe.eq(1), + sda_o.eq(sr_addr[-1]), + + If(clkgen.tx, + If(sr_addr_cnt == 6, + NextState("SEND_ADDR-RW"), + ).Else( + NextValue(sr_addr, sr_addr << 1), + NextValue(sr_addr_cnt, sr_addr_cnt + 1), + ), + ), + ) + + fsm.act("SEND_ADDR-RW", + # Generate Clk. + clkgen.en.eq(1), + sr_out_en.eq(0), + sda_oe.eq(1), + + If((sink.len_tx > 0) & ~tx_done, + sda_o.eq(0), + ).Else( + sda_o.eq(1), + ), + + If(clkgen.tx, + NextState("SEND_ADDR-ACK"), + ) + ) + + fsm.act("SEND_ADDR-ACK", + # Generate Clk. + clkgen.en.eq(1), + sr_out_en.eq(0), + sda_oe.eq(0), + + + If(clkgen.rx, + If(sda_i, + NextState("XFER-ERROR"), + ).Else( + If((sink.len_tx > 0) & ~tx_done, + NextState("PRE-XFER-TX"), + ).Elif(sink.len_rx > 0, + NextState("PRE-XFER-RX"), + ).Else( + NextState("XFER-END"), + ) + ), + ) + ) + + + + fsm.act("PRE-XFER-TX", + NextValue(sr_cnt, 0), + NextValue(bytes_send, 0), + sr_out_load.eq(1), + NextState("XFER-TX"), + ) + + fsm.act("XFER-TX", + # Generate Clk. + clkgen.en.eq(1), + sr_out_en.eq(1), + + # Data Out Shift. + If(clkgen.tx, + If(sr_cnt == 8, + NextValue(sr_cnt, 0), + NextState("XFER-TX-ACK"), + ).Else( + NextValue(sr_cnt, sr_cnt + 1), + sr_out_shift.eq(1), + ), + ), + ) + + fsm.act("XFER-TX-ACK", + # Generate Clk. + clkgen.en.eq(1), + sr_out_en.eq(0), + sda_oe.eq(0), + + + If(clkgen.rx, + If(sda_i, + NextState("XFER-ERROR"), + ).Else( + NextValue(bytes_send, bytes_send + 1), + + If((bytes_send == 3) & (sink.len_tx > 4), + NextState("XFER-TX-PRE-WAIT"), + ).Elif(bytes_send < sink.len_tx, + NextState("XFER-TX-BEFORE-NEXT"), + ).Else( + NextValue(tx_done, 1), + If(sink.len_rx > 0, + NextState("REPEATED-START-1"), + ).Else( + NextState("XFER-END"), + ), + ), + ), + ), + ) + + fsm.act("XFER-TX-BEFORE-NEXT", + # Generate Clk. + clkgen.en.eq(1), + If(clkgen.negedge, + NextState("XFER-TX"), + ), + ) + + fsm.act("XFER-TX-PRE-WAIT", + # Generate Clk. + clkgen.en.eq(1), + If(clkgen.tx, + NextState("XFER-TX-WAIT-SEND_STATUS"), + ), + ) + + fsm.act("XFER-TX-WAIT-SEND_STATUS", + # Generate Clk. + clkgen.en.eq(0), + clkgen.keep_low.eq(1), + NextValue(unfinished_tx, 1), + NextValue(unfinished_rx, 0), + source.data.eq(0), + source.nack.eq(0), + source.unfinished_tx.eq(1), + source.unfinished_rx.eq(0), + source.valid.eq(1), + source.last.eq(1), + If(source.ready, + NextState("XFER-TX-WAIT"), + ) + ) + + fsm.act("XFER-TX-WAIT", + # Generate Clk. + clkgen.en.eq(0), + clkgen.keep_low.eq(1), + NextValue(tx_done, 0), + If(enable & sink.valid, + NextState("PRE-XFER-TX"), + ), + ) + + fsm.act("XFER-ERROR", + NextValue(error, 1), + + If(clkgen.tx, + NextState("STOP"), + ), + ) + + fsm.act("REPEATED-START-1", + # Generate Clk. + clkgen.en.eq(1), + + If(clkgen.tx, + NextState("REPEATED-START-2"), + ), + ) + + fsm.act("REPEATED-START-2", + # Generate Clk. + clkgen.en.eq(1), + sda_oe.eq(1), + sda_o.eq(1), + If(clkgen.rx, + NextState("START"), + ), + ) + + fsm.act("PRE-XFER-RX", + NextValue(sr_cnt, 0), + NextValue(bytes_recv, 0), + NextValue(sr_in, 0), + NextState("XFER-RX"), + ) + + fsm.act("XFER-RX", + # Generate Clk. + self.clkgen.en.eq(1), + + + If(clkgen.rx, + If(sr_cnt == 8, + NextValue(sr_cnt, 0), + NextValue(bytes_recv, bytes_recv + 1), + If(bytes_recv + 1 < sink.len_rx, + NextState("XFER-RX-ACK"), + ).Else( + NextState("XFER-RX-NACK"), + ), + ).Else( + NextValue(sr_cnt, sr_cnt + 1), + sr_in_shift.eq(1), + ), + ), + + ) + + fsm.act("XFER-RX-PRE-ACK", + # Generate Clk. + clkgen.en.eq(1), + + If(clkgen.tx, + If(bytes_recv < sink.len_rx, + NextState("XFER-RX-ACK"), + ).Else( + NextState("XFER-RX-NACK"), + ), + ), + ) + + fsm.act("XFER-RX-ACK", + # Generate Clk. + clkgen.en.eq(1), + sda_oe.eq(1), + sda_o.eq(0), + + + If(clkgen.tx, + NextValue(sr_cnt, 0), + If(bytes_recv == 4, + sink.ready.eq(1), + NextState("XFER-RX-WAIT"), + ).Else( + NextState("XFER-RX"), + ), + ), + ) + + fsm.act("XFER-RX-NACK", + # Generate Clk. + clkgen.en.eq(1), + sda_oe.eq(1), + sda_o.eq(1), + + + If(clkgen.tx, + NextState("STOP"), + ), + ) + + fsm.act("XFER-RX-WAIT-SEND_STATUS", + # Generate Clk. + clkgen.en.eq(0), + clkgen.keep_low.eq(1), + NextValue(unfinished_tx, 0), + NextValue(unfinished_rx, 1), + source.data.eq(sr_in), + source.nack.eq(0), + source.unfinished_tx.eq(0), + source.unfinished_rx.eq(1), + source.valid.eq(1), + source.last.eq(1), + If(source.ready, + NextState("XFER-RX-WAIT"), + ) + ) + + fsm.act("XFER-RX-WAIT", + # Generate Clk. + clkgen.en.eq(0), + clkgen.keep_low.eq(1), + If(enable & sink.valid, + NextState("PRE-XFER-RX"), + ), + ) + + + fsm.act("STOP", + # Generate Clk. + clkgen.en.eq(1), + + sda_oe.eq(1), + + If(clkgen.tx, + sda_o.eq(1), + NextState("XFER-END"), + ).Else( + sda_o.eq(0), + ) + ) + + fsm.act("XFER-END", + # Accept CMD. + sink.ready.eq(1), + # Send Status/Data to Core. + NextState("SEND-STATUS-DATA"), + ) + + fsm.act("SEND-STATUS-DATA", + # Send Data In to Core and return to WAIT when accepted. + source.data.eq(sr_in), + source.nack.eq(error), + NextValue(unfinished_tx, 1), + NextValue(unfinished_rx, 0), + source.valid.eq(1), + source.last.eq(1), + If(source.ready, + NextState("WAIT-CMD-DATA"), + ) + ) diff --git a/litex/soc/cores/i2c/master.py b/litex/soc/cores/i2c/master.py new file mode 100644 index 0000000000..4ebbdb3383 --- /dev/null +++ b/litex/soc/cores/i2c/master.py @@ -0,0 +1,91 @@ +# +# This file is part of LiteI2C +# +# Copyright (c) 2020 Antmicro +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * +from migen.genlib.fsm import FSM, NextState + +from litex.soc.interconnect import stream +from litex.soc.interconnect.csr import * + +from litex.soc.cores.i2c.common import * + + +class LiteI2CMaster(Module, AutoCSR): + """Generic LiteI2C Master + + The ``LiteI2CMaster`` class provides a generic I2C master that can be controlled using CSRs. + + It supports multiple access modes with help of ``width`` and ``mask`` registers which can be used to configure the PHY into any supported SDR mode (single/dual/quad/octal). + + Parameters + ---------- + fifo_depth : int + Depth of the internal TX/RX FIFO. + + Attributes + ---------- + source : Endpoint(i2c_phy2core_layout), out + Data stream. + + sink : Endpoint(i2c_core2phy_layout), in + Control stream. + + enable : Signal(), out + Slave CS signal. + + """ + def __init__(self, tx_fifo_depth=1, rx_fifo_depth=1): + self.sink = stream.Endpoint(i2c_phy2core_layout) + self.source = stream.Endpoint(i2c_core2phy_layout) + self.enable = Signal() + + self._enable = CSRStorage() + self._len = CSRStorage(fields=[ + CSRField("len_tx", size=3, offset=0, description="I2C tx Xfer length (in bytes). Set to 111b to not send a stop signal and continue on the transfer."), + CSRField("len_rx", size=3, offset=8, description="I2C rx Xfer length (in bytes)."), + ], description="I2C transfer lengths") + self._addr = CSR(self.source.addr.nbits) + self._rxtx = CSR(self.source.data.nbits) + self._status = CSRStatus(fields=[ + CSRField("tx_ready", size=1, offset=0, description="TX FIFO is not full."), + CSRField("rx_ready", size=1, offset=1, description="RX FIFO is not empty."), + CSRField("nack", size=1, offset=8, description="Error on transfer." ), + CSRField("tx_unfinished", size=1, offset=16, description="Another tx transfer is expected."), + CSRField("rx_unfinished", size=1, offset=17, description="Another rx transfer is expected.") + ]) + + # # # + + # FIFOs. + tx_fifo = stream.SyncFIFO(i2c_core2phy_layout, depth=tx_fifo_depth) + rx_fifo = stream.SyncFIFO(i2c_phy2core_layout, depth=rx_fifo_depth) + self.submodules += tx_fifo, rx_fifo + self.comb += self.sink.connect(rx_fifo.sink) + self.comb += tx_fifo.source.connect(self.source) + + # I2C Enable. + self.comb += self.enable.eq(self._enable.storage) + + # I2C TX. + self.comb += [ + tx_fifo.sink.valid.eq(self._rxtx.re), + self._status.fields.tx_ready.eq(tx_fifo.sink.ready), + tx_fifo.sink.data.eq(self._rxtx.r), + tx_fifo.sink.addr.eq(self._addr.r), + tx_fifo.sink.len_tx.eq(self._len.fields.len_tx), + tx_fifo.sink.len_rx.eq(self._len.fields.len_rx), + tx_fifo.sink.last.eq(1), + ] + + # I2C RX. + self.comb += [ + rx_fifo.source.ready.eq(self._rxtx.we), + self._status.fields.rx_ready.eq(rx_fifo.source.valid), + self._status.fields.nack.eq(rx_fifo.source.nack), + self._status.fields.tx_unfinished.eq(rx_fifo.source.unfinished_tx), + self._status.fields.rx_unfinished.eq(rx_fifo.source.unfinished_rx), + self._rxtx.w.eq(rx_fifo.source.data), + ]