-
Notifications
You must be signed in to change notification settings - Fork 567
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add a i2c master similar to LiteSPI Signed-off-by: Fin Maaß <[email protected]>
- Loading branch information
1 parent
74127d5
commit e68e8a7
Showing
7 changed files
with
936 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))), | ||
] |
Oops, something went wrong.