Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DTS target integration #1975

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions litex/gen/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# SPDX-License-Identifier: BSD-2-Clause

from migen import *
from typing import Union, List

# Coloring Helpers ---------------------------------------------------------------------------------

Expand Down Expand Up @@ -43,3 +44,32 @@ def reverse_bytes(s):
n = (len(s) + 7)//8
return Cat(*[s[i*8:min((i + 1)*8, len(s))]
for i in reversed(range(n))])


# DTS ---------------------------------------------------------------------------------------------


def dts_property(name: str, value: Union[int, str, List[Union[int, str]], List[str], None] = None) -> str:
"""Returns property 'name' formatted in Devicetree syntax

value can be:
None (default)
int
str
list of str
list of int and str: in this case the str is expected to be a phandle.
"""
if value is None:
return f"{name};\n"
elif isinstance(value, list):
if all(isinstance(v, int) or v.startswith("&") for v in value):
dts_value = "<" + " ".join(f"{v}" for v in value) + ">"
elif all(isinstance(v, str) for v in value):
dts_value = ", ".join(f'"{v}"' for v in value)
else:
raise ValueError(f"unsupported elements in {value}")
elif isinstance(value, int) or value.startswith("&"):
dts_value = f"<{value}>"
else:
dts_value = f'"{value}"'
return f"{name} = {dts_value};\n"
40 changes: 37 additions & 3 deletions litex/soc/cores/gpio.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from migen import *
from migen.genlib.cdc import MultiReg

from litex.gen import *
from litex.gen import dts_property, LiteXModule

from litex.soc.interconnect.csr import *
from litex.soc.interconnect.csr_eventmanager import *
Expand Down Expand Up @@ -46,21 +46,48 @@ def add_irq(self, in_pads):
# GPIO Input ---------------------------------------------------------------------------------------

class GPIOIn(_GPIOIRQ):
def __init__(self, pads, with_irq=False):
dts_compatible = "litex,gpioin"
dts_node = "gpio"
dts_properties = dts_property("gpio-controller")
dts_properties += dts_property("#gpio-cells", 2)

def __init__(self, pads, with_irq=False, deprecated_dts=True):
pads = _to_signal(pads)
self._in = CSRStatus(len(pads), description="GPIO Input(s) Status.")
self.specials += MultiReg(pads, self._in.status)

self.dts_properties += dts_property("ngpio", len(pads))
if deprecated_dts: # deprecated: for linux driver
# the values below are based on json2dts_linux "switches"
# should be compatible with the linux driver
self.dts_compatible = ["litex,gpioin", "litex,gpio"]
self.dts_properties += dts_property("litex,direction", "in")
self.dts_properties += dts_property("litex,ngpio", len(pads))

if with_irq:
self.add_irq(self._in.status)

# GPIO Output --------------------------------------------------------------------------------------

class GPIOOut(LiteXModule):
def __init__(self, pads, reset=0):
dts_compatible = "litex,gpioout"
dts_node = "gpio"
dts_properties = dts_property("gpio-controller")
dts_properties += dts_property("#gpio-cells", 2)

def __init__(self, pads, reset=0, deprecated_dts=True):
pads = _to_signal(pads)
self.out = CSRStorage(len(pads), reset=reset, description="GPIO Output(s) Control.")
self.comb += pads.eq(self.out.storage)

self.dts_properties += dts_property("ngpio", len(pads))
if deprecated_dts: # deprecated: for linux driver
# the values below are based on json2dts_linux "leds"
# should be compatible with the linux driver
self.dts_compatible = ["litex,gpioout", "litex,gpio"]
self.dts_properties += dts_property("litex,direction", "out")
self.dts_properties += dts_property("litex,ngpio", len(pads))

# GPIO Input/Output --------------------------------------------------------------------------------

class GPIOInOut(LiteXModule):
Expand All @@ -74,6 +101,11 @@ def get_csrs(self):
# GPIO Tristate ------------------------------------------------------------------------------------

class GPIOTristate(_GPIOIRQ):
dts_compatible = "litex,gpiotristate"
dts_node = "gpio"
dts_properties = dts_property("gpio-controller")
dts_properties += dts_property("#gpio-cells", 2)

def __init__(self, pads, with_irq=False):
internal = not (hasattr(pads, "o") and hasattr(pads, "oe") and hasattr(pads, "i"))
nbits = len(pads) if internal else len(pads.o)
Expand Down Expand Up @@ -104,5 +136,7 @@ def __init__(self, pads, with_irq=False):
self.comb += pads.o[i].eq(self._out.storage[i])
self.specials += MultiReg(pads.i[i], self._in.status[i])

self.dts_properties += dts_property("ngpio", nbits)

if with_irq:
self.add_irq(self._in.status)
7 changes: 6 additions & 1 deletion litex/soc/cores/i2c.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
# SPDX-License-Identifier: BSD-2-Clause

from migen import *
from litex.gen import *

from litex.gen import LiteXModule, dts_property
from litex.soc.interconnect import wishbone
from litex.soc.interconnect.csr_eventmanager import *

Expand Down Expand Up @@ -203,6 +204,10 @@ def __init__(self, clock_width):
# ("idle", 1),
# ])
class I2CMaster(LiteXModule):
dts_compatible = "litex,cores-i2c" # litex,i2c is used for bitbang.I2CMaster()
dts_properties = dts_property("#address-cells", 1)
dts_properties += dts_property("#size-cells", 0)

def __init__(self, pads, bus=None):
if bus is None:
bus = wishbone.Interface(data_width=32)
Expand Down
11 changes: 10 additions & 1 deletion litex/soc/cores/spi/spi_mmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from migen import *

from litex.gen import *
from litex.gen import dts_property, LiteXModule
from litex.gen.genlib.misc import WaitTimer

from litex.soc.interconnect.csr import *
Expand Down Expand Up @@ -642,6 +642,11 @@ def __init__(self, pads, ctrl, data_width, sys_clk_freq):
# SPIMMAP ------------------------------------------------------------------------------------------

class SPIMMAP(LiteXModule):
dts_compatible = "litex,spi_mmap"
dts_node = "spi"
dts_properties = dts_property("#address-cells", 1)
dts_properties += dts_property("#size-cells", 0)

def __init__(self, pads, data_width, sys_clk_freq,
tx_origin = 0x0000_0000,
rx_origin = 0x0000_0000,
Expand All @@ -651,6 +656,10 @@ def __init__(self, pads, data_width, sys_clk_freq,
nslots = len(pads.cs_n)
assert nslots <= _nslots_max

self.dts_properties += dts_property("tx-fifo-depth", tx_fifo_depth)
self.dts_properties += dts_property("rx-fifo-depth", rx_fifo_depth)
self.dts_properties += dts_property("num-cs", nslots)

# Ctrl (Control/Status/IRQ) ----------------------------------------------------------------

self.ctrl = ctrl = SPICtrl(nslots=nslots)
Expand Down
2 changes: 2 additions & 0 deletions litex/soc/cores/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

class Timer(LiteXModule):
with_uptime = False
dts_compatible = "litex,timer"

def __init__(self, width=32):
self.intro = ModuleDoc("""Timer

Expand Down
11 changes: 8 additions & 3 deletions litex/soc/integration/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ def _generate_includes(self, with_bios=True):
self.soc.constants.update( self._get_json_constants())
self.soc.csr_regions.update(self._get_json_csr_regions())

# exclude Devicetree syntax specific constants ("_DTS_" in name)
filtered_constants = {k: v for k, v in self.soc.constants.items() if "_DTS_" not in k}

# Generate BIOS files when the SoC uses it.
if with_bios:
# Generate Variables to variables.mak.
Expand Down Expand Up @@ -246,13 +249,13 @@ def _generate_includes(self, with_bios=True):
write_to_file(os.path.realpath(self.memory_x), memory_x_contents)

# Generate SoC Config/Constants to soc.h.
soc_contents = export.get_soc_header(self.soc.constants)
soc_contents = export.get_soc_header(filtered_constants)
write_to_file(os.path.join(self.generated_dir, "soc.h"), soc_contents)

# Generate CSR registers definitions/access functions to csr.h.
csr_contents = export.get_csr_header(
regions = self.soc.csr_regions,
constants = self.soc.constants,
constants = filtered_constants,
csr_base = self.soc.mem_regions["csr"].origin,
with_access_functions = True,
with_fields_access_functions = False,
Expand Down Expand Up @@ -283,9 +286,11 @@ def _generate_csr_map(self):

# CSV Export.
if self.csr_csv is not None:
# exclude dts-specific constants ("_DTS_" in name)
filtered_constants = {k: v for k, v in self.soc.constants.items() if "_DTS_" not in k}
csr_csv_contents = export.get_csr_csv(
csr_regions = self.soc.csr_regions,
constants = self.soc.constants,
constants = filtered_constants,
mem_regions = self.soc.mem_regions)
write_to_file(os.path.realpath(self.csr_csv), csr_csv_contents)

Expand Down
10 changes: 10 additions & 0 deletions litex/soc/integration/soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,15 @@ def add_config(self, name, value=None, check_duplicate=True):
name = "CONFIG_" + name
self.add_constant(name, value, check_duplicate=check_duplicate)

def add_dts_node(self, name: str, module) -> None:
"""Adds device tree information to generate entries for a core / module."""
prefix = name + "_dts_"
self.add_constant(prefix + "compatible", module.dts_compatible)
if hasattr(module, "dts_node"):
self.add_constant(prefix + "node", module.dts_node)
if hasattr(module, "dts_properties"):
self.add_constant(prefix + "properties", module.dts_properties)

def check_bios_requirements(self):
# Check for required Peripherals.
for periph in [ "timer0"]:
Expand Down Expand Up @@ -1316,6 +1325,7 @@ def add_timer(self, name="timer0"):
self.add_module(name=name, module=Timer())
if self.irq.enabled:
self.irq.add(name, use_loc_if_exists=True)
self.add_dts_node(name, self.get_module(name))

# Add Watchdog ---------------------------------------------------------------------------------
def add_watchdog(self, name="watchdog0", width=32, crg_rst=None, reset_delay=None):
Expand Down
98 changes: 98 additions & 0 deletions litex/tools/litex_json2dts_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#
# Copyright (c) 2019-2020 Florent Kermarrec <[email protected]>
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# Copyright (c) 2024 Andrew Dennison <[email protected]>
# SPDX-License-Identifier: BSD-2-Clause

import os
Expand All @@ -13,6 +14,55 @@
import argparse

from litex.gen.common import KILOBYTE, MEGABYTE
from litex.tools.litex_json2dts_zephyr import dts_reg, dts_reg_names, indent, indent_all
from litex.gen import dts_property

def csr_base_size(d: dict, name: str) -> int:
"""Calculate size in bytes of csr_base `name` from the contents of d["csr_registers"]."""
size = 0
csr_base = d["csr_bases"][name]
# csr_registers contains dictionaries with register information
for reg in [v for k, v in d["csr_registers"].items() if k.startswith(name)]:
next_offest = reg["addr"] + reg["size"] * 4 - csr_base
if size < next_offest:
size = next_offest
return size


def csr_regions(d: dict, name: str) -> list:
"""Gathers a list of csr regions for `name` from the contents of d["csr_bases"]."""
return [
{
"name": "csr" if name == base else base[len(name) + 1 :],
"addr": addr,
"size": csr_base_size(d, base),
}
for base, addr in d["csr_bases"].items()
if name == base or base.startswith(name + "_")
]


def mem_regions(d: dict, name: str) -> list:
"""Gathers a list of memory regions for `name` from the contents of d["memories"]."""
return [
{
"name": "mem" if name == mem else mem[len(name) + 1 :],
"addr": params["base"],
"size": params["size"],
}
for mem, params in d["memories"].items()
if name == mem or mem.startswith(name + "_")
]


def dts_interrupt(d: dict, name: str) -> str:
irq = d['constants'].get(name + '_interrupt', None)
return f"interrupts = <{irq}>;\n" if irq else ""


def soc_name(name: str) -> str:
soc = name.rpartition("soc_")
return soc[0] + soc[1] # empty string or "xxx_soc_"

def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_device=None, polling=False):
aliases = {}
Expand Down Expand Up @@ -466,6 +516,54 @@ def get_riscv_cpu_isa_extensions(cpu_isa, cpu_name):
usb_ohci_mem_base = d["memories"]["usb_ohci_ctrl"]["base"],
usb_ohci_interrupt = "" if polling else "interrupts = <{}>;".format(16)) # FIXME

# GENERIC device mode ---------------------------------------------------------------------------
for constant, value in [(k,v) for (k,v) in d["constants"].items() if k.endswith("_dts_compatible")]:
prefix = constant.removesuffix("compatible")
name = prefix.removesuffix("_dts_")

# compatible property is first
properties = dts_property("compatible", value)
# get default node name from first compatible string
if isinstance(value, list):
node_name = value[0].split(",")[-1]
else:
node_name = value.split(",")[-1]

# reg property is second
reg = csr_regions(d, name) + mem_regions(d, name)
if reg == []:
raise ValueError(f"no csr or mem regions for {name}")
properties += dts_reg(reg, levels=0)
# only add reg_names property for > 1 region
if len(reg) > 1 or True:
properties += dts_reg_names(reg, levels=0)

soc = soc_name(name)
for constant, value in [(k,v) for (k,v) in d["constants"].items() if k.startswith(prefix)]:
constant = constant.removeprefix(prefix)
if constant == "compatible":
pass
elif constant == "node":
node_name = value
elif constant == "properties":
# includes fixup of phandle references when node is in downstream soc
properties += value.replace("&", f"&{soc}")
else:
raise ValueError(f"unexpected constant {name}_dts_{constant}")

properties += dts_interrupt(d, name)
properties += dts_property("clocks", f"&{soc}sys_clk")
# omit status property - okay is the default
# properties += dts_property("status", "okay")

dts += indent("{label}: {node_name}@{unit_address:x} {{\n".format(
label=name,
node_name=node_name,
unit_address=reg[0]["addr"],
), levels=3)
dts += indent_all(properties, levels=4) + "\n"
dts += indent("};\n", levels=3)

# SPI Flash ------------------------------------------------------------------------------------

if "spiflash" in d["csr_bases"]:
Expand Down
Loading