Skip to content

Commit

Permalink
core: i2c: add i2c master
Browse files Browse the repository at this point in the history
add a i2c master similar to LiteSPI

Signed-off-by: Fin Maaß <[email protected]>
  • Loading branch information
maass-hamburg committed Aug 5, 2024
1 parent 74127d5 commit e68e8a7
Show file tree
Hide file tree
Showing 7 changed files with 936 additions and 0 deletions.
93 changes: 93 additions & 0 deletions litex/soc/cores/litei2c/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2024 Fin Maaß <[email protected]>
# SPDX-License-Identifier: BSD-2-Clause

from migen import *

from litex.soc.integration.doc import AutoDoc
from litex.soc.interconnect import stream
from litex.soc.interconnect.csr import *

from litex.soc.cores.litei2c.common import *
from litex.soc.cores.litei2c.crossbar import LiteI2CCrossbar
from litex.soc.cores.litei2c.master import LiteI2CMaster
from litex.soc.cores.litei2c.generic_phy 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 ``LiteI2CMaster`` and connect it to the PHY.
Access to PHY can be shared via crossbar.
Parameters
----------
sys_clk_freq : int
Frequency of the system clock.
phy : Module
Module or object that contains PHY stream interfaces and a enable signal to connect
the ``LiteI2C`` to. If not provided, it will be created automatically based on the pads.
pads : Object
I2C pads description.
clock_domain : str
Name of LiteI2C clock domain.
with_master : bool
Enables register-operated I2C master controller.
"""

def __init__(self, sys_clk_freq, phy=None, pads=None, clock_domain="sys",
with_master=True, i2c_master_tx_fifo_depth=1, i2c_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 = i2c_master_tx_fifo_depth,
rx_fifo_depth = i2c_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),
]

def add_i2c_device(self, i2c_device):
port = self.crossbar.get_port(i2c_device.enable)
self.comb += [
port.source.connect(i2c_device.sink),
i2c_device.source.connect(port.sink),
]

110 changes: 110 additions & 0 deletions litex/soc/cores/litei2c/clkgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2024 Fin Maaß <[email protected]>
# 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.
i2c_speed_mode : Signal(2), in
I2C speed mode.
sys_clk_freq : int
System clock frequency.
Attributes
----------
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.
tx : Signal(), out
Outputs 1 when the clock is high and the I2C bus is in the transmit state.
rx : Signal(), out
Outputs 1 when the clock is low and the I2C bus is in the receive state.
keep_low : Signal(), in
Forces the clock to be low, when the clock is disabled.
"""
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)), # 100 kHz (Standard Mode)
1 : div.eq(freq_to_div(sys_clk_freq, 400000)), # 400 kHz (Fast Mode)
2 : div.eq(freq_to_div(sys_clk_freq, 1000000)), # 1000 kHz (Fast Mode Plus)
})]

self.comb += [
# negedge.eq(en & (sub_cnt == 0b00) & (cnt == div)),
tx.eq(en & (sub_cnt == 0b01) & (cnt == div)),
# posedge.eq(en & (sub_cnt == 0b10) & (cnt == div)),
rx.eq(en & (sub_cnt == 0b11) & (cnt == div)),
]

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(
io = pads.scl,
o = Signal(), # I2C uses Pull-ups, only drive low.
oe = ~clk, # Drive when scl is low.
i = Signal(), # Not used.
)

47 changes: 47 additions & 0 deletions litex/soc/cores/litei2c/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2024 Fin Maaß <[email protected]>
# 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 - data to be transmitted
addr - slave address
len_tx - number of bytes to transmit
len_rx - number of bytes to receive
"""
i2c_core2phy_layout = [
("data", 32),
("addr", 7),
("len_tx", 3),
("len_rx", 3),
("recover", 1)
]
"""
Stream layout for PHY->LiteI2CCore connection
data - received data
nack - NACK signal
unfinished_tx - another tx transfer is expected
unfinished_rx - another rx transfer is expected
"""
i2c_phy2core_layout = [
("data", 32),
("nack", 1),
("unfinished_tx", 1),
("unfinished_rx", 1)
]

# 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)
98 changes: 98 additions & 0 deletions litex/soc/cores/litei2c/crossbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2015 Florent Kermarrec <[email protected]>
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# Copyright from LiteSPI file added above
#
# Copyright (c) 2024 Fin Maaß <[email protected]>
# SPDX-License-Identifier: BSD-2-Clause

from collections import OrderedDict

from migen import *
from migen.genlib.roundrobin import RoundRobin
from litex.soc.cores.litei2c.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 e68e8a7

Please sign in to comment.