Skip to content

Commit

Permalink
core: i2c: add litei2c
Browse files Browse the repository at this point in the history
Signed-off-by: Fin Maaß <[email protected]>
  • Loading branch information
maass-hamburg committed Jul 26, 2024
1 parent e77a94f commit db30724
Show file tree
Hide file tree
Showing 6 changed files with 916 additions and 0 deletions.
91 changes: 91 additions & 0 deletions litex/soc/cores/i2c/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#
# This file is part of LiteI2C
#
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# 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),
]
127 changes: 127 additions & 0 deletions litex/soc/cores/i2c/clkgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#
# This file is part of Litei2C
#
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# 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.
)

46 changes: 46 additions & 0 deletions litex/soc/cores/i2c/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#
# This file is part of Litei2c
#
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# 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)
95 changes: 95 additions & 0 deletions litex/soc/cores/i2c/crossbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#
# This file is part of LiteI2C
#
# Copyright (c) 2015 Florent Kermarrec <[email protected]>
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# 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))),
]
Loading

0 comments on commit db30724

Please sign in to comment.