mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
litex/tools: Add RemoteI2C
An I2C driver via *bone using the Litex remote client
This commit is contained in:
parent
3041150773
commit
6f0b39772a
1 changed files with 201 additions and 0 deletions
201
litex/tools/litex_remotei2c.py
Normal file
201
litex/tools/litex_remotei2c.py
Normal file
|
@ -0,0 +1,201 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2024 Andrew Dennison <andrew.dennison@motec.com.au>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
from enum import IntEnum, auto
|
||||
|
||||
__all__ = ["RemoteI2C"]
|
||||
|
||||
|
||||
class I2CAddr(IntEnum):
|
||||
XFER = 0
|
||||
CONFIG = 1
|
||||
|
||||
|
||||
class I2CCmd(IntEnum):
|
||||
# bits 0-7 are address or data
|
||||
ACK = 8
|
||||
READ = auto()
|
||||
WRITE = auto()
|
||||
START = auto()
|
||||
STOP = auto()
|
||||
IDLE = auto()
|
||||
|
||||
|
||||
class RemoteI2C:
|
||||
def __init__(self, name, bus=None, clk_freq=100e3):
|
||||
if bus:
|
||||
self.wb = bus
|
||||
else:
|
||||
from litex import RemoteClient
|
||||
|
||||
self.wb = RemoteClient()
|
||||
self.wb.open()
|
||||
|
||||
self.addr_stride = self.wb.constants.config_bus_data_width // 8
|
||||
self.name = name
|
||||
self.mem_base = getattr(self.wb.mems, name).base
|
||||
self.clk_freq = clk_freq
|
||||
|
||||
@property
|
||||
def clk_freq(self):
|
||||
"""I2C clock."""
|
||||
config = self._reg_read(I2CAddr.CONFIG)
|
||||
clk_freq = self.wb.constants.config_clock_frequency / (2 * config + 1)
|
||||
return clk_freq
|
||||
|
||||
@clk_freq.setter
|
||||
def clk_freq(self, clk_freq: int | float):
|
||||
# config is clk2x prescale+1.
|
||||
# NOTE: -1 is omitted below as the integer math gives us the floor
|
||||
# so this is implicitly catered for.
|
||||
config = int(self.wb.constants.config_clock_frequency // (2 * clk_freq))
|
||||
self._reg_write(I2CAddr.CONFIG, config)
|
||||
print(f"{self.name}: clk_freq now {self.clk_freq/1000:.0f}kHz")
|
||||
|
||||
def _reg_read(self, reg: I2CAddr) -> int:
|
||||
return self.wb.read(self.mem_base + reg * self.addr_stride)
|
||||
|
||||
def _reg_write(self, reg: I2CAddr, data: int) -> None:
|
||||
self.wb.write(self.mem_base + reg * self.addr_stride, data)
|
||||
|
||||
def _wait_idle(self) -> int:
|
||||
"""low-level API: wait for an i2c command to complete: controller idle
|
||||
|
||||
Returns
|
||||
-------
|
||||
XFER register contents: command flags and data
|
||||
"""
|
||||
timeout = 0
|
||||
while not (data := self._reg_read(I2CAddr.XFER)) & (1 << I2CCmd.IDLE):
|
||||
assert timeout < 200
|
||||
return data
|
||||
|
||||
def cmd_start(self) -> None:
|
||||
"""low-level API: generate an i2c (re)start condition on the bus"""
|
||||
cmd_data = 1 << I2CCmd.START
|
||||
self._reg_write(I2CAddr.XFER, cmd_data)
|
||||
self._wait_idle()
|
||||
|
||||
def cmd_stop(self) -> None:
|
||||
"""low-level API: generate an i2c stop condition on the bus"""
|
||||
cmd_data = 1 << I2CCmd.STOP
|
||||
self._reg_write(I2CAddr.XFER, cmd_data)
|
||||
self._wait_idle()
|
||||
|
||||
def cmd_write(self, data) -> bool:
|
||||
"""low-level API: transmit data (8-bits) on the bus
|
||||
|
||||
Arguments
|
||||
-------
|
||||
data : int
|
||||
8-data to transmit
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
ack bit that was received from the bus
|
||||
"""
|
||||
cmd_data = data + (1 << I2CCmd.WRITE)
|
||||
self._reg_write(I2CAddr.XFER, cmd_data)
|
||||
cmd_data = self._wait_idle()
|
||||
return bool(cmd_data & (1 << I2CCmd.ACK))
|
||||
|
||||
def cmd_read(self, ack: bool = True) -> int:
|
||||
"""low-level API: receive data (8-bits) from the bus
|
||||
|
||||
Arguments
|
||||
-------
|
||||
ack : bool, optional
|
||||
ack bit to transmit on the bus (default=True)
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
data received from the bus
|
||||
"""
|
||||
cmd_data = (1 << I2CCmd.READ) | (int(ack) << I2CCmd.ACK)
|
||||
self._reg_write(I2CAddr.XFER, cmd_data)
|
||||
cmd_data = self._wait_idle()
|
||||
return cmd_data & 0xFF
|
||||
|
||||
def write(self, addr: int, data: bytearray, silent: bool = False, stop: bool = True) -> bool:
|
||||
"""write bytearray to i2c. Interface matches GreenPakI2cInterface.write()"""
|
||||
rd_nwr = 0
|
||||
i = 0
|
||||
self.cmd_start()
|
||||
if not (ack := self.cmd_write((addr << 1) | rd_nwr)):
|
||||
if not silent:
|
||||
print(f"write: no ack from address {addr}")
|
||||
else:
|
||||
i = 0
|
||||
for byte in data:
|
||||
i += 1
|
||||
if not (ack := self.cmd_write(byte)):
|
||||
if not silent:
|
||||
print(f"write failed at byte {i}")
|
||||
break
|
||||
if stop:
|
||||
self.cmd_stop()
|
||||
return ack
|
||||
|
||||
def read(self, addr: int, byte_count: int, silent: bool = False, stop: bool = True) -> bytearray | None:
|
||||
"""read bytearray from i2c. Interface matches GreenPakI2cInterface.read()"""
|
||||
rd_nwr = 1
|
||||
data = None
|
||||
self.cmd_start()
|
||||
if not self.cmd_write((addr << 1) | rd_nwr):
|
||||
print(f"read: no ack from address {addr}")
|
||||
else:
|
||||
data = bytearray()
|
||||
if byte_count:
|
||||
for _ in range(1, byte_count):
|
||||
data.append(self.cmd_read())
|
||||
data.append(self.cmd_read(ack=False))
|
||||
if stop:
|
||||
self.cmd_stop()
|
||||
return data
|
||||
|
||||
def scan(self, start: int = 1, stop: int = 0x7F) -> list[int]:
|
||||
"""Scan the bus for devices.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : int, optional
|
||||
Start address for scan. (default=1)
|
||||
|
||||
stop : int, optional
|
||||
Stop address for scan. (default=0x7f)
|
||||
|
||||
Raises
|
||||
------
|
||||
None
|
||||
|
||||
Returns
|
||||
------
|
||||
list[int]
|
||||
A list of addresses where devices were detected
|
||||
"""
|
||||
addresses = []
|
||||
for addr in range(start, stop + 1):
|
||||
self.cmd_start()
|
||||
if self.cmd_write(addr << 1):
|
||||
addresses.append(addr)
|
||||
print(f"device at {addr:#x}")
|
||||
self.cmd_stop()
|
||||
return addresses
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--bus", "-b", default="i2c_0", help="i2c bus to scan.")
|
||||
args = parser.parse_args()
|
||||
|
||||
i2c = RemoteI2C(args.bus)
|
||||
addresses = i2c.scan()
|
||||
print(addresses)
|
Loading…
Reference in a new issue