Skip to content

Commit 447cb6c

Browse files
committed
core: i2c: add i2c master
add a i2c master similar to LiteSPI Signed-off-by: Fin Maaß <f.maass@vogl-electronic.com>
1 parent 74127d5 commit 447cb6c

File tree

7 files changed

+936
-0
lines changed

7 files changed

+936
-0
lines changed

litex/soc/cores/litei2c/__init__.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#
2+
# This file is part of LiteX.
3+
#
4+
# Copyright (c) 2024 Fin Maaß <f.maass@vogl-electronic.com>
5+
# SPDX-License-Identifier: BSD-2-Clause
6+
7+
from migen import *
8+
9+
from litex.soc.integration.doc import AutoDoc
10+
from litex.soc.interconnect import stream
11+
from litex.soc.interconnect.csr import *
12+
13+
from litex.soc.cores.litei2c.common import *
14+
from litex.soc.cores.litei2c.crossbar import LiteI2CCrossbar
15+
from litex.soc.cores.litei2c.master import LiteI2CMaster
16+
from litex.soc.cores.litei2c.generic_phy import LiteI2CPHYCore
17+
18+
19+
class LiteI2CCore(Module):
20+
def __init__(self):
21+
self.source = stream.Endpoint(i2c_core2phy_layout)
22+
self.sink = stream.Endpoint(i2c_phy2core_layout)
23+
self.enable = Signal()
24+
25+
26+
class LiteI2C(Module, AutoCSR, AutoDoc):
27+
"""I2C Controller wrapper.
28+
29+
The ``LiteI2C`` class provides a wrapper that can instantiate ``LiteI2CMaster`` and connect it to the PHY.
30+
31+
Access to PHY can be shared via crossbar.
32+
33+
Parameters
34+
----------
35+
sys_clk_freq : int
36+
Frequency of the system clock.
37+
38+
phy : Module
39+
Module or object that contains PHY stream interfaces and a enable signal to connect
40+
the ``LiteI2C`` to. If not provided, it will be created automatically based on the pads.
41+
42+
pads : Object
43+
I2C pads description.
44+
45+
clock_domain : str
46+
Name of LiteI2C clock domain.
47+
48+
with_master : bool
49+
Enables register-operated I2C master controller.
50+
51+
"""
52+
53+
def __init__(self, sys_clk_freq, phy=None, pads=None, clock_domain="sys",
54+
with_master=True, i2c_master_tx_fifo_depth=1, i2c_master_rx_fifo_depth=1):
55+
56+
if phy is None:
57+
if pads is None:
58+
raise ValueError("Either phy or pads must be provided.")
59+
self.submodules.phy = phy = LiteI2CPHYCore(pads, clock_domain, sys_clk_freq)
60+
61+
62+
self.submodules.crossbar = crossbar = LiteI2CCrossbar(clock_domain)
63+
64+
self.comb += phy.enable.eq(crossbar.enable)
65+
66+
if with_master:
67+
self.submodules.master = master = LiteI2CMaster(
68+
tx_fifo_depth = i2c_master_tx_fifo_depth,
69+
rx_fifo_depth = i2c_master_rx_fifo_depth)
70+
port_master = crossbar.get_port(master.enable)
71+
self.comb += [
72+
port_master.source.connect(master.sink),
73+
master.source.connect(port_master.sink),
74+
]
75+
76+
if clock_domain != "sys":
77+
self.comb += [
78+
crossbar.tx_cdc.source.connect(phy.sink),
79+
phy.source.connect(crossbar.rx_cdc.sink),
80+
]
81+
else:
82+
self.comb += [
83+
crossbar.master.source.connect(phy.sink),
84+
phy.source.connect(crossbar.master.sink),
85+
]
86+
87+
def add_i2c_device(self, i2c_device):
88+
port = self.crossbar.get_port(i2c_device.enable)
89+
self.comb += [
90+
port.source.connect(i2c_device.sink),
91+
i2c_device.source.connect(port.sink),
92+
]
93+

litex/soc/cores/litei2c/clkgen.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#
2+
# This file is part of LiteX.
3+
#
4+
# Copyright (c) 2024 Fin Maaß <f.maass@vogl-electronic.com>
5+
# SPDX-License-Identifier: BSD-2-Clause
6+
7+
from migen import *
8+
9+
import math
10+
11+
from litex.soc.integration.doc import AutoDoc, ModuleDoc
12+
13+
from litex.build.io import SDRTristate
14+
15+
16+
def freq_to_div(sys_clk_freq, freq):
17+
return math.ceil(sys_clk_freq / (4*freq)) - 1
18+
19+
class LiteI2CClkGen(Module, AutoDoc):
20+
"""I2C Clock generator
21+
22+
The ``LiteI2CClkGen`` class provides a generic I2C clock generator.
23+
24+
Parameters
25+
----------
26+
pads : Object
27+
i2C pads description.
28+
29+
i2c_speed_mode : Signal(2), in
30+
I2C speed mode.
31+
32+
sys_clk_freq : int
33+
System clock frequency.
34+
35+
Attributes
36+
----------
37+
posedge : Signal(), out
38+
Outputs 1 when there is a rising edge on the generated clock, 0 otherwise.
39+
40+
negedge : Signal(), out
41+
Outputs 1 when there is a falling edge on the generated clock, 0 otherwise.
42+
43+
en : Signal(), in
44+
Clock enable input, output clock will be generated if set to 1, 0 resets the core.
45+
46+
tx : Signal(), out
47+
Outputs 1 when the clock is high and the I2C bus is in the transmit state.
48+
49+
rx : Signal(), out
50+
Outputs 1 when the clock is low and the I2C bus is in the receive state.
51+
52+
keep_low : Signal(), in
53+
Forces the clock to be low, when the clock is disabled.
54+
"""
55+
def __init__(self, pads, i2c_speed_mode, sys_clk_freq):
56+
# self.posedge = posedge = Signal()
57+
# self.negedge = negedge = Signal()
58+
self.tx = tx = Signal()
59+
self.rx = rx = Signal()
60+
self.en = en = Signal()
61+
self.keep_low = keep_low = Signal()
62+
63+
cnt_width = bits_for(freq_to_div(sys_clk_freq, 100000))
64+
65+
div = Signal(cnt_width)
66+
cnt = Signal(cnt_width)
67+
sub_cnt = Signal(2)
68+
clk = Signal(reset=1)
69+
70+
self.comb += [
71+
Case(i2c_speed_mode, {
72+
0 : div.eq(freq_to_div(sys_clk_freq, 100000)), # 100 kHz (Standard Mode)
73+
1 : div.eq(freq_to_div(sys_clk_freq, 400000)), # 400 kHz (Fast Mode)
74+
2 : div.eq(freq_to_div(sys_clk_freq, 1000000)), # 1000 kHz (Fast Mode Plus)
75+
})]
76+
77+
self.comb += [
78+
# negedge.eq(en & (sub_cnt == 0b00) & (cnt == div)),
79+
tx.eq(en & (sub_cnt == 0b01) & (cnt == div)),
80+
# posedge.eq(en & (sub_cnt == 0b10) & (cnt == div)),
81+
rx.eq(en & (sub_cnt == 0b11) & (cnt == div)),
82+
]
83+
84+
self.sync += [
85+
If(en,
86+
If(cnt < div,
87+
cnt.eq(cnt+1),
88+
).Else(
89+
cnt.eq(0),
90+
clk.eq(sub_cnt[1]),
91+
If(sub_cnt < 3,
92+
sub_cnt.eq(sub_cnt+1),
93+
).Else(
94+
sub_cnt.eq(0),
95+
)
96+
)
97+
).Else(
98+
clk.eq(~keep_low),
99+
cnt.eq(0),
100+
sub_cnt.eq(0),
101+
)
102+
]
103+
104+
self.specials += SDRTristate(
105+
io = pads.scl,
106+
o = Signal(), # I2C uses Pull-ups, only drive low.
107+
oe = ~clk, # Drive when scl is low.
108+
i = Signal(), # Not used.
109+
)
110+

litex/soc/cores/litei2c/common.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#
2+
# This file is part of LiteX.
3+
#
4+
# Copyright (c) 2024 Fin Maaß <f.maass@vogl-electronic.com>
5+
# SPDX-License-Identifier: BSD-2-Clause
6+
7+
from migen import *
8+
from migen.genlib.cdc import MultiReg
9+
10+
# Core <-> PHY Layouts -----------------------------------------------------------------------------
11+
12+
"""
13+
Stream layout for LiteI2CCore->PHY connection:
14+
data - data to be transmitted
15+
addr - slave address
16+
len_tx - number of bytes to transmit
17+
len_rx - number of bytes to receive
18+
"""
19+
i2c_core2phy_layout = [
20+
("data", 32),
21+
("addr", 7),
22+
("len_tx", 3),
23+
("len_rx", 3),
24+
("recover", 1)
25+
]
26+
"""
27+
Stream layout for PHY->LiteI2CCore connection
28+
data - received data
29+
nack - NACK signal
30+
unfinished_tx - another tx transfer is expected
31+
unfinished_rx - another rx transfer is expected
32+
"""
33+
i2c_phy2core_layout = [
34+
("data", 32),
35+
("nack", 1),
36+
("unfinished_tx", 1),
37+
("unfinished_rx", 1)
38+
]
39+
40+
# Helpers ------------------------------------------------------------------------------------------
41+
42+
class ResyncReg(Module):
43+
def __init__(self, src, dst, clock_domain):
44+
if clock_domain == "sys":
45+
self.comb += dst.eq(src)
46+
else:
47+
self.specials += MultiReg(src, dst, clock_domain)

litex/soc/cores/litei2c/crossbar.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#
2+
# This file is part of LiteX.
3+
#
4+
# Copyright (c) 2015 Florent Kermarrec <florent@enjoy-digital.fr>
5+
# Copyright (c) 2020 Antmicro <www.antmicro.com>
6+
# Copyright from LiteSPI file added above
7+
#
8+
# Copyright (c) 2024 Fin Maaß <f.maass@vogl-electronic.com>
9+
# SPDX-License-Identifier: BSD-2-Clause
10+
11+
from collections import OrderedDict
12+
13+
from migen import *
14+
from migen.genlib.roundrobin import RoundRobin
15+
from litex.soc.cores.litei2c.common import *
16+
17+
from litex.soc.interconnect import stream
18+
19+
20+
class LiteI2CMasterPort:
21+
def __init__(self):
22+
self.source = stream.Endpoint(i2c_core2phy_layout)
23+
self.sink = stream.Endpoint(i2c_phy2core_layout)
24+
25+
26+
class LiteI2CSlavePort:
27+
def __init__(self):
28+
self.source = stream.Endpoint(i2c_phy2core_layout)
29+
self.sink = stream.Endpoint(i2c_core2phy_layout)
30+
31+
32+
class LiteI2CCrossbar(Module):
33+
def __init__(self, cd):
34+
self.cd = cd
35+
self.users = []
36+
self.master = LiteI2CMasterPort()
37+
if cd != "sys":
38+
rx_cdc = stream.AsyncFIFO(i2c_phy2core_layout, 32, buffered=True)
39+
tx_cdc = stream.AsyncFIFO(i2c_core2phy_layout, 32, buffered=True)
40+
self.submodules.rx_cdc = ClockDomainsRenamer({"write": cd, "read": "sys"})(rx_cdc)
41+
self.submodules.tx_cdc = ClockDomainsRenamer({"write": "sys", "read": cd})(tx_cdc)
42+
self.comb += [
43+
self.rx_cdc.source.connect(self.master.sink),
44+
self.master.source.connect(self.tx_cdc.sink),
45+
]
46+
47+
self.enable = Signal()
48+
self.user_enable = []
49+
self.user_request = []
50+
51+
def get_port(self, enable, request = None):
52+
user_port = LiteI2CSlavePort()
53+
internal_port = LiteI2CSlavePort()
54+
55+
tx_stream = user_port.sink
56+
57+
self.comb += tx_stream.connect(internal_port.sink)
58+
59+
rx_stream = internal_port.source
60+
61+
self.comb += rx_stream.connect(user_port.source)
62+
63+
if request is None:
64+
request = Signal()
65+
self.comb += request.eq(enable)
66+
67+
self.users.append(internal_port)
68+
self.user_enable.append(self.enable.eq(enable))
69+
self.user_request.append(request)
70+
71+
return user_port
72+
73+
def do_finalize(self):
74+
self.submodules.rr = RoundRobin(len(self.users))
75+
76+
# TX
77+
self.submodules.tx_mux = tx_mux = stream.Multiplexer(i2c_core2phy_layout, len(self.users))
78+
79+
# RX
80+
self.submodules.rx_demux = rx_demux = stream.Demultiplexer(i2c_phy2core_layout, len(self.users))
81+
82+
for i, user in enumerate(self.users):
83+
self.comb += [
84+
user.sink.connect(getattr(tx_mux, f"sink{i}")),
85+
getattr(rx_demux, f"source{i}").connect(user.source),
86+
]
87+
88+
self.comb += [
89+
self.rr.request.eq(Cat(self.user_request)),
90+
91+
self.tx_mux.source.connect(self.master.source),
92+
self.tx_mux.sel.eq(self.rr.grant),
93+
94+
self.master.sink.connect(self.rx_demux.sink),
95+
self.rx_demux.sel.eq(self.rr.grant),
96+
97+
Case(self.rr.grant, dict(enumerate(self.user_enable))),
98+
]

0 commit comments

Comments
 (0)