diff --git a/.reuse/dep5 b/.reuse/dep5 index 4c9264316..92cfbb7e1 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -33,6 +33,7 @@ Files: drivers/timer/meson/config.json drivers/timer/goldfish/config.json drivers/timer/cdns/config.json + drivers/pinctrl/imx/config.json Copyright: UNSW License: BSD-2-Clause diff --git a/README.md b/README.md index ff610ffd5..7e0f09c3e 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ when installing via pip, instead run: pip3 install --break-system-packages sdfgen==0.26.0 ``` +If you're planning on using the `pinctrl` driver, you will need to run: +```sh +# Note that this depends on PyYAML>=5.1 so you might need to create a venv +pip3 install devicetree +``` + #### Microkit SDK ```sh @@ -81,6 +87,12 @@ when installing via pip, instead run: pip3 install --break-system-packages sdfgen==0.26.0 ``` +If you're planning on using the `pinctrl` driver, you will need to run: +```sh +# Note that this depends on PyYAML>=5.1 so you might need to create a venv +pip3 install devicetree +``` + #### Microkit SDK For Apple Silicon: diff --git a/ci/examples/pinctrl.py b/ci/examples/pinctrl.py new file mode 100755 index 000000000..8ca8c5c18 --- /dev/null +++ b/ci/examples/pinctrl.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +import asyncio +from pathlib import Path +import sys + +sys.path.insert(1, Path(__file__).parents[2].as_posix()) + +from ci.lib.backends import * +from ci.lib.runner import TestConfig, cli, matrix_product +from ci import common, matrix + +TEST_MATRIX = matrix_product( + board=matrix.EXAMPLES["pinctrl"]["boards_test"], + config=matrix.EXAMPLES["pinctrl"]["configs"], + build_system=matrix.EXAMPLES["pinctrl"]["build_systems"], +) + + +async def test(backend: HardwareBackend, test_config: TestConfig): + async with asyncio.timeout(10): + await wait_for_output(backend, b"Begin input\r\n") + await wait_for_output(backend, b"Please give me character!\r\n") + + await send_input(backend, b"1234567890") + await expect_output(backend, b"1234567890") + await wait_for_output( + backend, b"serial_client has received 10 characters so far!\r\n" + ) + + +if __name__ == "__main__": + cli("pinctrl", test, TEST_MATRIX, common.backend_fn, common.loader_img_path) diff --git a/ci/matrix.py b/ci/matrix.py index 35cb9c50b..860a077d2 100644 --- a/ci/matrix.py +++ b/ci/matrix.py @@ -124,6 +124,23 @@ "zcu102", ], }, + "pinctrl": { + # Only works in debug mode so as to not depend on serial + "configs": ["debug", "release"], + "build_systems": ["make"], + "boards_build": [ + "imx8mm_evk", + "imx8mq_evk", + "imx8mp_evk", + "maaxboard", + ], + "boards_test": [ + "imx8mm_evk", + "imx8mq_evk", + "imx8mp_evk", + "maaxboard", + ], + }, } if TYPE_CHECKING: diff --git a/docs/drivers.md b/docs/drivers.md index 87103b503..94e053b4d 100644 --- a/docs/drivers.md +++ b/docs/drivers.md @@ -43,6 +43,13 @@ and a list of Device Tree compatible strings it is known to work with. * virtIO GPU (2D only) * `virtio,mmio` +## Pinctrl + +* i.MX8 IOMUXC + * `fsl,imx8mm-iomuxc` + * `fsl,imx8mp-iomuxc` + * `fsl,imx8mq-iomuxc` + ## Serial * ARM UART diff --git a/docs/pinctrl/imx/imx.md b/docs/pinctrl/imx/imx.md new file mode 100644 index 000000000..72f9567dd --- /dev/null +++ b/docs/pinctrl/imx/imx.md @@ -0,0 +1,54 @@ + + +# i.MX8 Platform-specific pinctrl details + +Prerequisite reading: +1. [pinctrl.md](../pinctrl.md) + +Each device's state will appear as a pin configuration node in the pinctrl device. A pin +configuration node will contain all the data necessary for the pin controller configure all required +pins to the correct state. Each pin's configuration data is encoded as an entry of 6 32-bit +integers in the `fsl,pins` prop of the pin configuration node. + +An entry is of the form < mux_reg conf_reg input_reg mux_val input_val pad_setting >. Where: +- "mux_reg" indicates the offset of mux register. +- "conf_reg" indicates the offset of pad configuration register. +- "input_reg" indicates the offset of select input register. +- "mux_val" indicates the mux value to be applied. +- "input_val" indicates the select input value to be applied. +- "pad_setting" indicates the pad configuration value to be applied. + +For example (from `maaxboard.dts`): +``` +uart2grp { + fsl,pins = <0x23c 0x4a4 0x4fc 0x00 0x00 0x49 0x240 0x4a8 0x00 0x00 0x00 0x49>; + phandle = <0x25>; +}; +``` + +So `uart2` requires two pins. The first pin need: +mux_reg @ 0x23c = 0x0 +conf_reg @ 0x4a4 = 0x49 +input_reg @ 0x4fc = 0x0 + +To decode what these numbers mean, we start by consulting the datasheet (i.MX 8M Dual/8M QuadLite/8M +Quad Applications Processors Reference Manual IMX8MDQLQRM Rev 3.1, 06/2021). At page 1581, section +8.2.5.139 we know that the register `mux_reg @ 0x23c` is called `IOMUXC_SW_MUX_CTL_PAD_UART2_RXD`. +Which controls what signal line inside the chip the `UART2_RXD` pad on the chip package will be +connected to. By setting this register to zero, we connect this pad to the RX line of UART2. + +Then, `conf_reg @ 0x4a4` is the `IOMUXC_SW_PAD_CTL_PAD_UART2_RXD` register that controls the +electrical characteristic of the `UART2_RXD` pad. We convert `0x49` to binary `0b1001001`. By +superimposing the binary value onto the register map in section 8.2.5.293 on page 1813. We can +decode that by setting `conf_reg` to 0x49, we achieve: +- Drive strength: 255_OHM — 255 Ohm @3.3V, 240 Ohm @2.5V, 230 Ohm @1.8V, 265 Ohm @1.2V, +- Medium Frequency Slew Rate (100Mhz), +- Open Drain Disabled, +- Pull Up Resistor Disabled, and +- Schmitt Trigger Enabled for this pad. + +While it is not necessary for you to understand what all of this registers mean, it is still useful +to be aware of how they roughly function for debugging purposes. \ No newline at end of file diff --git a/docs/pinctrl/pinctrl.md b/docs/pinctrl/pinctrl.md new file mode 100644 index 000000000..2bc6fda4c --- /dev/null +++ b/docs/pinctrl/pinctrl.md @@ -0,0 +1,139 @@ + + +# Pin control driver +Also known as a pinmux or pinctrl driver + +# Terminology +- Port: refers to an input or output line of a logic instance in the chip (e.g. UART, DDR, HDMI, + I2C,...). For example, an I2C instance has SDA and SCL ports. Not to be confused with pad. +- Pad: refers to the physical pin on the chip package (example, ball for BGA packaged chips). +- Client device: a peripheral device on the board that needs pinctrl configuration. + +# Overview + +A chip contains a limited number of pads as it is not feasable to have a one-to-one mapping between +all pads and ports. Hence, most of the pads have multiple signal options. I.e. a pad can be +connected to one of multiple ports at a given point in time as appropriate for the intended use +case. These signal-to-pin and pin-to-signal options are selected by the input-output multiplexer +called a pin controller or pinmux. The pin controller is also used to configure other electronic +characteristics of a pin, such as drive strength, bias, etc. All of these configurations can be +programmed in software by writing to the pin controller's registers. + +Before the pinctrl driver is built, a Python script will read the target board's device tree source +file, extracting all the pinctrl settings and encoding them as binary values in an assembly file. +Then the driver is built and linked with the pinctrl data assembly file, creating a complete pinctrl +driver ELF image. + +The pinctrl driver must be exclusively run at the highest priority to ensure that it is the first PD +initialised in the system. So that all pads can be connected to the desired pins before any other +device drivers attempt to initialise. At `init()` time, the driver will read the encoded pinctrl +data and write it into the pinctrl device registers. + +This driver is needed in two cases: +1. When the peripheral device you are using on your board isn't used by the bootloader. Thus some + bootloader implementations may not bother to program the pinctrl registers pertaining to those + client devices. +2. Some device drivers need to be able to dynamically configure the pinctrl characteristics at + run-time. For example, to switch the SD Card into a higher speed state on the i.MX8 and Meson + platforms, the pinctrl registers must be reprogrammed to handle the higher speed. Though this is + future work. + +# Data Structure +This section describe how the pinctrl data is encoded in the DTS and how it get organised by the +Python script for the C driver. This information is universal to all platforms. + +At the top level, we have an array of all the client devices that needs the pinmux. Each device may +have multiple pinctrl states to select from, and each state has an associated set of pin +configurations. + +For example, with the SD Card reader on the MaaXBoard: +``` +mmc@30b40000 { + compatible = "fsl,imx8mq-usdhc\0fsl,imx7d-usdhc"; + reg = <0x30b40000 0x10000>; + interrupts = <0x00 0x16 0x04>; + clocks = <0x02 0xec 0x02 0x69 0x02 0xd2>; + clock-names = "ipg\0ahb\0per"; + assigned-clocks = <0x02 0x8e>; + assigned-clock-rates = <0x17d78400>; + fsl,tuning-start-tap = <0x14>; + fsl,tuning-step = <0x02>; + bus-width = <0x04>; + status = "okay"; + pinctrl-names = "default\0state_100mhz\0state_200mhz"; + pinctrl-0 = <0x3d>; + pinctrl-1 = <0x3e>; + pinctrl-2 = <0x3f>; + non-removable; + no-sdio; + no-1-8-v; +}; + +iomuxc@30330000 { + compatible = "fsl,imx8mq-iomuxc"; + reg = <0x30330000 0x10000>; + pinctrl-names = "default"; + pinctrl-0 = <0x13>; + phandle = <0x11>; + + imx8mq-evk { + ... + usdhc1grp { + fsl,pins = <0xa0 0x308 0x00 0x00 0x00 0x83 + 0xa4 0x30c 0x00 0x00 0x00 0xc3 + 0xa8 0x310 0x00 0x00 0x00 0xc3 + 0xac 0x314 0x00 0x00 0x00 0xc3 + 0xb0 0x318 0x00 0x00 0x00 0xc3 + 0xb4 0x31c 0x00 0x00 0x00 0xc3 + 0x40 0x2a8 0x00 0x00 0x00 0x19>; + phandle = <0x3d>; + }; + + usdhc1grp100mhz { + fsl,pins = <0xa0 0x308 0x00 0x00 0x00 0x85 + 0xa4 0x30c 0x00 0x00 0x00 0xc5 + 0xa8 0x310 0x00 0x00 0x00 0xc5 + 0xac 0x314 0x00 0x00 0x00 0xc5 + 0xb0 0x318 0x00 0x00 0x00 0xc5 + 0xb4 0x31c 0x00 0x00 0x00 0xc5 + 0x40 0x2a8 0x00 0x00 0x00 0x19>; + phandle = <0x3e>; + }; + + usdhc1grp200mhz { + fsl,pins = <0xa0 0x308 0x00 0x00 0x00 0x87 + 0xa4 0x30c 0x00 0x00 0x00 0xc7 + 0xa8 0x310 0x00 0x00 0x00 0xc7 + 0xac 0x314 0x00 0x00 0x00 0xc7 + 0xb0 0x318 0x00 0x00 0x00 0xc7 + 0xb4 0x31c 0x00 0x00 0x00 0xc7 + 0x40 0x2a8 0x00 0x00 0x00 0x19>; + phandle = <0x3f>; + }; + ... + } +} +``` + +The prop `pinctrl-names` tells us how many pinctrl states a client device supports and the names of +the states. + +The order of the state names correspond to the `pinctrl-[0-9]+` prop. So the phandle in `pinctrl-0` +will lead us to the node in the pinctrl device that contains the correct register values that we +need to write for the `default` pinctrl configuration of that client device. + +# Current Implementation +The pinctrl driver will always set the `default` configuration on boot up. Supporting dynamic +setting of pinctrl state at run-time is future work. + +# Supported Platforms +Please see [drivers.md](../drivers.md). + +# Platform specific details +Please see the README.md inside the respective platform's folder. \ No newline at end of file diff --git a/drivers/pinctrl/imx/config.json b/drivers/pinctrl/imx/config.json new file mode 100644 index 000000000..532562205 --- /dev/null +++ b/drivers/pinctrl/imx/config.json @@ -0,0 +1,18 @@ +{ + "compatible": [ + "fsl,imx8mm-iomuxc", + "fsl,imx8mp-iomuxc", + "fsl,imx8mq-iomuxc" + ], + "resources": { + "regions": [ + { + "name": "regs", + "perms": "rw", + "size": 65536, + "dt_index": 0 + } + ], + "irqs": [] + } +} diff --git a/drivers/pinctrl/imx/create_pinctrl_config.py b/drivers/pinctrl/imx/create_pinctrl_config.py new file mode 100644 index 000000000..e6dd1fa15 --- /dev/null +++ b/drivers/pinctrl/imx/create_pinctrl_config.py @@ -0,0 +1,423 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +import sys +from devicetree import dtlib + +# Document referenced: +# [1] Linux: Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt +# [2] Linux: Documentation/devicetree/bindings/pinctrl/fsl,imx8mm-pinctrl.yaml +# [3] Linux: drivers/pinctrl/freescale/pinctrl-imx.c + +# [1] +# Software Input On Field. +# Force the selected mux mode input path no matter of MUX_MODE functionality. +# By default the input path is determined by functionality of the selected +# mux mode (regular). This bit will be set in "pad_setting" +PAD_SION = 1 << 30 +# If the above bit is set, clear it from "pad_setting" then set this bit in "mux_reg" +MUX_SION = 1 << 4 + +# Compatible boards and the path of its pinmux device in the device tree. +compatible_board_to_pinctrl_dev_path: dict[str, str] = { + "fsl,imx8mm-evk": "/soc@0/bus@30000000/pinctrl@30330000", + "fsl,imx8mq-evk": "/soc@0/bus@30000000/pinctrl@30330000", + "avnet/embest,maaxboard": "/soc@0/bus@30000000/iomuxc@30330000", + "fsl,imx8mp-evk": "/soc@0/bus@30000000/pinctrl@30330000", +} + +UINT32_T_SIZE = 4 +PINCTRL_CONFIG_DATA_MAGIC = "0x696D70696E6D7578" # "impinmux" + + +class PinctrlRegisterData: + def __init__( + self, + mux_reg: int, + conf_reg: int, + input_reg: int, + mux_val: int, + input_val: int, + pad_setting: int, + ): + self.mux_reg = mux_reg + self.conf_reg = conf_reg + self.input_reg = input_reg + self.mux_val = mux_val + self.input_val = input_val + self.pad_setting = pad_setting + + def __str__(self): + return f"mux reg @ {hex(self.mux_reg)} = {hex(self.mux_val)}, conf reg @ {hex(self.conf_reg)} = {hex(self.pad_setting)}, input reg @ {hex(self.input_reg)} = {hex(self.input_val)}" + + +class PinctrlStateData: + # Represent a singular pinctrl state of a device. + + def __init__( + self, + name: str, + index: int, + group_names: list[str], + phandles: list[int], + registers: list[PinctrlRegisterData], + ): + self.name = name + self.index = index + self.group_names = group_names + self.phandles = phandles + self.registers = registers + + def __str__(self): + serialised = f"* Config source name `{self.name}` at index {self.index} with group name `{str(self.group_names)}` and phandles {str(self.phandles)} have registers:\n" + for reg in self.registers: + serialised += str(reg) + "\n" + return serialised + + +class PinctrlClientDeviceData: + # Represent a device that requires pinctrl configuration. For example, `mmc@30b40000` in maaxboard.dts. + + def __init__( + self, + aliases_node: dtlib.Node, + pinctrl_node: dtlib.Node, + client_device_node: dtlib.Node, + ): + self.client_device_node: dtlib.Node = client_device_node + # A device can have multiple different pin states it selects from at runtime. + # Though all devices that require pinctrl have a "default" config. + self.pinctrl_configs: list[PinctrlStateData] = [] + self.alias: str = "" + + # Check and record whether this device have an alias + for alias_name in aliases_node.props: + if ( + aliases_node.props.get(alias_name).to_string() + == client_device_node.path + ): + self.alias = alias_name + break + + # Make sure the given device have a need for pinctrl configuration + assert "status" in client_device_node.props.keys() + assert client_device_node.props["status"].to_string() == "okay" + assert "pinctrl-0" in client_device_node.props.keys() + + # Grab and parse all the possible pin configurations of this device. + for config_idx, config_name in enumerate( + client_device_node.props.get("pinctrl-names").to_strings() + ): + target_prop: str = f"pinctrl-{config_idx}" + target_phandles: list[int] = client_device_node.props.get( + target_prop + ).to_nums() + + config_data = get_pinctrl_info( + pinctrl_node, target_phandles, config_name, config_idx + ) + assert config_data is not None + self.pinctrl_configs.append(config_data) + + def __str__(self): + serialised = f"** Device `{self.client_device_node.path}` with alias {self.alias} have the following pinctrl configs:\n" + for config in self.pinctrl_configs: + serialised += str(config) + "\n" + return serialised + + +class AssemblyDataLabelAllocator: + def __init__(self, label_prefix: str): + self.watermark: int = -1 + self.label_prefix: str = label_prefix + + def create_label(self) -> str: + self.watermark += 1 + return self.label_prefix + str(self.watermark) + + +class AssemblyStringAllocator: + # A simple class to manage the allocation of strings in the pinctrl data assembly file. + def __init__(self, label_prefix: str): + self.label_allocator = AssemblyDataLabelAllocator(label_prefix) + # string -> label + self.allocated_strings: dict[str, str] = {} + + # Returns a label to the target string + def create_label_for_str(self, target: str) -> str: + if target not in self.allocated_strings: + self.allocated_strings[target] = self.label_allocator.create_label() + return self.allocated_strings[target] + + def to_assembler(self) -> str: + asm_strs = "" + for string in self.allocated_strings: + asm_strs += f'{self.allocated_strings[string]}: .asciz "{string}"\n' + return asm_strs + + +class AssemblyDataObject: + def __init__(self, label: str): + self.data = f"{label}:\n" + + def add_word(self, value): + self.data += f"\t.word {str(value)}\n" + + def add_quad(self, value): + self.data += f"\t.quad {str(value)}\n" + + def add_ptr_from_label(self, label: str): + self.data += f"\t.quad {label}\n" + + def to_assembler(self) -> str: + return self.data + "\n" + + +class PinctrlData: + def __init__(self): + self.devices_with_pinctrl: list[PinctrlClientDeviceData] = [] + + def __str__(self): + serialised = "" + for dev in self.devices_with_pinctrl: + serialised += str(dev) + return serialised + + def parse(self, dt: dtlib.DT, pinctrl_dev_path: str): + pinctrl_node = dt.get_node( + pinctrl_dev_path + ) # This is the `iomuxc@30330000` node + aliases_node = dt.get_node("/aliases") + # Find all devices that need pinctrl configuration + device_node: dtlib.Node + for device_node in devicetree.node_iter(): + if ( + "status" in device_node.props.keys() + and device_node.props["status"].to_string() == "okay" + ): + if "pinctrl-names" in device_node.props.keys(): + self.devices_with_pinctrl.append( + PinctrlClientDeviceData(aliases_node, pinctrl_node, device_node) + ) + + def write_assembler(self, out_dir: str): + # Gather all the data we need for the assembly + num_devices = 0 + + # Manage allocation of strings and their labels in the assembly file to prevent duplication + str_asm_allocator = AssemblyStringAllocator("pinctrl_config_str_") + + label_pin_regs_array_allocator = AssemblyDataLabelAllocator( + "pinctrl_config_pins_reg_" + ) + asm_pin_regs: list[AssemblyDataObject] = [] + + label_state_object_allocator = AssemblyDataLabelAllocator( + "pinctrl_config_state_obj_" + ) + asm_states: list[AssemblyDataObject] = [] + + label_states_array_allocator = AssemblyDataLabelAllocator( + "pinctrl_config_states_" + ) + asm_states_arrays: list[AssemblyDataObject] = [] + + asm_devices = AssemblyDataObject("pinctrl_client_devices_configs") + + # For every device requiring pinctrl config, write their `pinctrl_client_device_data_t` C struct + for device in self.devices_with_pinctrl: + # For every pinctrl state of this device, write its `pinctrl_client_device_state_t` C struct + this_device_pinctrl_states_labels: list[str] = [] + for state in device.pinctrl_configs: + # For every pin in this state, write its `pinctrl_pin_register_t` C struct + state_pins_label = label_pin_regs_array_allocator.create_label() + asm_pin_regs.append(AssemblyDataObject(state_pins_label)) + + num_pins = 0 + for pin in state.registers: + asm_pin_regs[-1].add_word(pin.mux_reg) + asm_pin_regs[-1].add_word(pin.conf_reg) + asm_pin_regs[-1].add_word(pin.input_reg) + asm_pin_regs[-1].add_word(pin.mux_val) + asm_pin_regs[-1].add_word(pin.input_val) + asm_pin_regs[-1].add_word(pin.pad_setting) + num_pins += 1 + + # Now that we have the label to all the pins in this state, create the `pinctrl_client_device_state_t` + state_label = label_state_object_allocator.create_label() + asm_states.append(AssemblyDataObject(state_label)) + asm_states[-1].add_ptr_from_label( + str_asm_allocator.create_label_for_str(state.name) + ) + asm_states[-1].add_word(num_pins) + asm_states[-1].add_ptr_from_label(state_pins_label) + + this_device_pinctrl_states_labels.append(state_label) + + # We've encoded all the states and have their labels, create a states array (the `**state`) + states_array_label = label_states_array_allocator.create_label() + asm_states_arrays.append(AssemblyDataObject(states_array_label)) + for state_label in this_device_pinctrl_states_labels: + asm_states_arrays[-1].add_ptr_from_label(state_label) + + # Finally create the `pinctrl_client_device_data` + asm_devices.add_ptr_from_label( + str_asm_allocator.create_label_for_str(device.client_device_node.path) + ) + if device.alias is not None: + asm_devices.add_ptr_from_label( + str_asm_allocator.create_label_for_str(device.alias) + ) + else: + asm_devices.add_ptr_from_label( + str_asm_allocator.create_label_for_str("") + ) + asm_devices.add_word(len(this_device_pinctrl_states_labels)) + asm_devices.add_ptr_from_label(states_array_label) + + num_devices += 1 + + # Write all the data to .s file + with open(out_dir + "/pinctrl_config_data.s", "w") as file: + file.write(".section .rodata\n") + + file.write("\t.align 4\n") + file.write("\t.global pinctrl_config_data_magic\n") + file.write("\t.global pinctrl_client_devices_configs\n") + file.write("\t.global num_pinctrl_client_devices_configs\n") + + file.write( + f"pinctrl_config_data_magic:\n\t.quad {PINCTRL_CONFIG_DATA_MAGIC}\n" + ) + file.write(f"num_pinctrl_client_devices_configs:\n\t.word {num_devices}\n") + + file.write(asm_devices.to_assembler()) + file.write(str_asm_allocator.to_assembler()) + + [file.write(asm_pin_reg.to_assembler()) for asm_pin_reg in asm_pin_regs] + [file.write(asm_state.to_assembler()) for asm_state in asm_states] + [ + file.write(asm_states_array.to_assembler()) + for asm_states_array in asm_states_arrays + ] + + +def get_value_from_bytes_array(byte_array: bytes, index: int) -> int: + # Extracts a 4-byte integer value from a 'bytes' array at a certain index + return int.from_bytes( + byte_array[index * UINT32_T_SIZE : (index + 1) * UINT32_T_SIZE], + "big", + signed=False, + ) + + +def get_pinctrl_info( + pinctrl_device: dtlib.Node, + target_pinctrl_config_phandles: list[int], + target_pinctrl_config_name: str, + target_pinctrl_config_index: int, +) -> PinctrlStateData: + # pinctrl_device is the DT node to the pinctrl device that have all the configuration values. + # And pinctrl_config_phandle is the phandle to the specific group inside pinctrl_device + + group_names: list[str] = [] + phandles: list[int] = [] + regs: list[PinctrlRegisterData] = [] + + for pinctrl_config_node in pinctrl_device.node_iter(): + pinctrl_config_node_data = pinctrl_config_node.props.get("fsl,pins") + if pinctrl_config_node_data != None: + pinctrl_config_node_phandle = pinctrl_config_node.props.get( + "phandle" + ).to_num() + if pinctrl_config_node_phandle in target_pinctrl_config_phandles: + # We only support the normal configuration, not the SCU configuration. + assert len(pinctrl_config_node_data.value) % 6 == 0 + + group_names.append(pinctrl_config_node.name) + phandles.append(pinctrl_config_node_phandle) + + # [2] + # At this stage, we have the array of values + # Since each device configuration comes in a set of six values, we'll loop through in sets of 6 + for i in range( + len(pinctrl_config_node_data.value) // (6 * UINT32_T_SIZE) + ): + mux_reg = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + ) + conf_reg = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 1 + ) + input_reg = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 2 + ) + mux_val = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 3 + ) + input_val = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 4 + ) + pad_setting = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 5 + ) + + # [3]: checkout tag v6.1 at line 557 + # For pins that have SION bit set in "pad_setting", set it in "mux_val" and clear it from "pad_setting" + if pad_setting & PAD_SION: + mux_val |= MUX_SION + pad_setting &= ~PAD_SION + + regs.append( + PinctrlRegisterData( + mux_reg, + conf_reg, + input_reg, + mux_val, + input_val, + pad_setting, + ) + ) + + if len(regs) > 0: + return PinctrlStateData( + target_pinctrl_config_name, + target_pinctrl_config_index, + group_names, + phandles, + regs, + ) + else: + return None + + +if __name__ == "__main__": + + if len(sys.argv) != 3: + print("Usage: ") + print("\tpython3 create_pinmux_setup.py ") + exit(1) + + # Parse device tree file + devicetree = dtlib.DT(sys.argv[1], force=True) + out_dir = sys.argv[2] + + # Ensure compatibility + target_compats = devicetree.get_node("/").props.get("compatible").to_strings() + matched_compat_str = None + for target_compat in target_compats: + if target_compat in compatible_board_to_pinctrl_dev_path.keys(): + matched_compat_str = target_compat + break + if matched_compat_str is None: + print(f"Your target board {target_compats} isn't compatible with this driver.") + sys.exit(1) + + # Start parsing + pinctrl_data = PinctrlData() + pinctrl_data.parse( + devicetree, compatible_board_to_pinctrl_dev_path[matched_compat_str] + ) + print(pinctrl_data) + + pinctrl_data.write_assembler(out_dir) diff --git a/drivers/pinctrl/imx/pinctrl.c b/drivers/pinctrl/imx/pinctrl.c new file mode 100644 index 000000000..ae448d1dc --- /dev/null +++ b/drivers/pinctrl/imx/pinctrl.c @@ -0,0 +1,184 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* +Pinctrl driver for the iMX8 SoC based platforms. + +Documents referenced: +[1] Linux: Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt +[2] Linux: drivers/pinctrl/freescale/pinctrl-imx.c +[3] U-Boot: drivers/pinctrl/nxp/pinctrl-imx.c +*/ + +#include +#include +#include +#include +#include +#include + +__attribute__((__section__(".device_resources"))) device_resources_t device_resources; + +#define DEBUG_DRIVER + +#ifdef DEBUG_DRIVER +#define LOG_DRIVER(...) do{ sddf_dprintf("PINCTRL DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_DRIVER(...) do{}while(0) +#endif + +#define LOG_DRIVER_ERR(...) do{ sddf_printf("PINCTRL DRIVER|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) +#define LOG_DRIVER_ERR_FATAL(...) do{ sddf_printf("PINCTRL DRIVER|FATAL ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + +uintptr_t iomuxc_dev_base; + +// [1] +// Special values for pad_setting: +// Indicate this pin does not need config +#define NO_PAD_CTL (1 << 31) + +typedef struct __attribute__((packed)) pinctrl_pin_register { + const uint32_t mux_reg; /* Contains offset of mux registers */ + const uint32_t conf_reg; /* Offset of pad configuration register */ + const uint32_t input_reg; /* Offset of select input register */ + const uint32_t mux_val; /* Mux values to be applied */ + const uint32_t input_val; /* Select input values to be applied */ + const uint32_t pad_setting; /* Pad configuration values to be applied */ +} pinctrl_pin_register_t; + +typedef struct __attribute__((packed)) pinctrl_client_device_state { + const char *state_name; + const uint32_t num_pins; + const pinctrl_pin_register_t *pins_reg; +} pinctrl_client_device_state_t; + +typedef struct __attribute__((packed)) pinctrl_client_device_data { + const char *dev_dt_path; /* Device tree path of this particular device that needs pinctrl configuration */ + const char *dev_dt_alias; + const uint32_t num_states; /* Number of pinctrl states required as defined in the `pinctrl-names` prop */ + const pinctrl_client_device_state_t **states; +} pinctrl_client_device_data_t; + +// Data from Device Tree that is linked during compile time. +#define CONFIG_MAGIC 0x696D70696E6D7578 // "impinmux" +extern const uint64_t pinctrl_config_data_magic; +extern const pinctrl_client_device_data_t pinctrl_client_devices_configs[]; +extern const uint32_t num_pinctrl_client_devices_configs; + +static bool sanity_check_pinctrl_reg_offset(uint32_t offset) +{ + if (offset % 4 == 0) { + return true; + } else { + LOG_DRIVER_ERR_FATAL("offset 0x%x is not 4 bytes aligned\n", offset); + return false; + } +} + +static bool read_mux(uint32_t offset, uint32_t *ret) +{ + if (!sanity_check_pinctrl_reg_offset(offset)) { + return false; + } + + volatile uint32_t *mux_reg_vaddr = (uint32_t *)(iomuxc_dev_base + (uintptr_t)offset); + *ret = *mux_reg_vaddr; + return true; +} + +static bool set_mux(uint32_t offset, uint32_t val) +{ + if (!sanity_check_pinctrl_reg_offset(offset)) { + return false; + } + + volatile uint32_t *mux_reg_vaddr = (uint32_t *)(iomuxc_dev_base + (uintptr_t)offset); + *mux_reg_vaddr = val; + + if (*mux_reg_vaddr != val) { + LOG_DRIVER_ERR_FATAL("write was not completed, real != expected: %x != %x", *mux_reg_vaddr, val); + return false; + } + return true; +} + +static void debug_print_pinctrl_config_data(void) +{ + LOG_DRIVER("STARTING PINCTRL CONFIG DUMP\n"); + LOG_DRIVER("Total %u devices need pinctrl configuration.\n", num_pinctrl_client_devices_configs); + for (int i = 0; i < num_pinctrl_client_devices_configs; i++) { + LOG_DRIVER("** %s with alias %s have the following %d states:\n", pinctrl_client_devices_configs[i].dev_dt_path, + pinctrl_client_devices_configs[i].dev_dt_alias, pinctrl_client_devices_configs[i].num_states); + for (int j = 0; j < pinctrl_client_devices_configs[i].num_states; j++) { + LOG_DRIVER("* State '%s' at index %d have %u pins:\n", + pinctrl_client_devices_configs[i].states[j]->state_name, j, + pinctrl_client_devices_configs[i].states[j]->num_pins); + for (int k = 0; k < pinctrl_client_devices_configs[i].states[j]->num_pins; k++) { + LOG_DRIVER("mux reg: 0x%x = 0x%x, input reg: 0x%x = 0x%x, pad conf reg: 0x%x = 0x%x\n", + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].mux_reg, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].mux_val, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].input_reg, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].input_val, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].conf_reg, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].pad_setting); + } + } + } + LOG_DRIVER("---------------------------------------------------------------\n"); +} + +static void pinctrl_set_state(pinctrl_client_device_state_t state) +{ + for (int j = 0; j < state.num_pins; j++) { + assert(set_mux(state.pins_reg[j].mux_reg, state.pins_reg[j].mux_val)); + if (state.pins_reg[j].input_reg) { + // We don't support "quirky" select input values of [2] (checkout tag v6.1, line 196). + // As it was only ever used in vendor kernels of old imx6 and imx7 boards and never made + // it to upstream Linux. + assert(state.pins_reg[j].input_val >> 24 != 0xff); + + assert(set_mux(state.pins_reg[j].input_reg, state.pins_reg[j].input_val)); + } + if (!(state.pins_reg[j].pad_setting & NO_PAD_CTL)) { + assert(set_mux(state.pins_reg[j].conf_reg, state.pins_reg[j].pad_setting)); + } + } +} + +static void pinctrl_reset_all_default(void) +{ + for (int i = 0; i < num_pinctrl_client_devices_configs; i++) { + pinctrl_client_device_data_t client_device = pinctrl_client_devices_configs[i]; + assert(client_device.num_states > 0); + pinctrl_client_device_state_t default_state = *(pinctrl_client_devices_configs[i].states[0]); + assert(strcmp(default_state.state_name, "default") == 0); + + LOG_DRIVER("Setting dev %s to default pinctrl config with total %u pins.\n", client_device.dev_dt_path, + default_state.num_pins); + pinctrl_set_state(default_state); + } +} + +void init(void) +{ + assert(device_resources_check_magic(&device_resources)); + assert(device_resources.num_irqs == 0); + assert(device_resources.num_regions == 1); + + assert(pinctrl_config_data_magic == CONFIG_MAGIC); + assert(num_pinctrl_client_devices_configs >= 1); + + iomuxc_dev_base = (uintptr_t)device_resources.regions[0].region.vaddr; + + debug_print_pinctrl_config_data(); + pinctrl_reset_all_default(); + + LOG_DRIVER("INIT OK\n"); +} + +void notified(microkit_channel ch) +{ + LOG_DRIVER_ERR("received ntfn on unexpected channel %u\n", ch); +} diff --git a/drivers/pinctrl/imx/pinctrl_driver.mk b/drivers/pinctrl/imx/pinctrl_driver.mk new file mode 100644 index 000000000..612491304 --- /dev/null +++ b/drivers/pinctrl/imx/pinctrl_driver.mk @@ -0,0 +1,48 @@ +# +# Copyright 2025, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Include this snippet in your project Makefile to build +# the iMX8 pinctrl driver +# Assumes libsddf_util_debug.a is in ${LIBS}. +# +# NOTES +# Generates pinctrl_driver.elf +# Has 3 parameters: +# SDDF: path to sddf root +# PYTHON: python interpreter +# DTS: absolute path to the device tree source file. + +ifndef PYTHON +$(error PYTHON is not set) +endif + +ifndef DTS +$(error DTS is not set) +endif + +PINCTRL_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +pinctrl/imx/pinctrl_config_data.s: ${DTS} ${PINCTRL_DIR}/create_pinctrl_config.py pinctrl/imx + ${PYTHON} ${PINCTRL_DIR}/create_pinctrl_config.py ${DTS} pinctrl/imx + +pinctrl/imx/pinctrl_config_data.o: pinctrl/imx/pinctrl_config_data.s + ${CC} ${ASFLAGS} -c $< -o $@ + +pinctrl/imx/pinctrl.o: $(PINCTRL_DIR)/pinctrl.c pinctrl/imx + ${CC} ${CFLAGS} -DSOC_$(shell echo $(SOC) | tr a-z A-Z | tr - _) -c $< -o $@ + +pinctrl_driver.elf: pinctrl/imx/pinctrl.o pinctrl/imx/pinctrl_config_data.o + ${LD} ${LDFLAGS} $^ ${LIBS} -o $@ + +-include pinctrl/imx/pinctrl_driver.d + +pinctrl/imx: + mkdir -p $@ + +clean:: + rm -rf pinctrl + +clobber:: + rm -f pinctrl \ No newline at end of file diff --git a/dts/imx8mm_evk.dts b/dts/imx8mm_evk.dts index 7ce5cd141..8701f5d56 100644 --- a/dts/imx8mm_evk.dts +++ b/dts/imx8mm_evk.dts @@ -696,7 +696,7 @@ }; usdhc3grp { - fsl,pins = <0x138 0x3a0 0x00 0x12 0x00 0x190 0x13c 0x3a4 0x00 0x02 0x00 0x1d0 0x11c 0x384 0x00 0x02 0x00 0x1d0 0x120 0x388 0x00 0x02 0x00 0x1d0 0x124 0x38c 0x00 0x02 0x00 0x1d0 0x124 0x38c 0x00 0x02 0x00 0x1d0 0x128 0x390 0x00 0x02 0x00 0x1d0 0x130 0x398 0x00 0x02 0x00 0x1d0 0x100 0x368 0x00 0x02 0x00 0x1d0 0x104 0x36c 0x00 0x02 0x00 0x1d0 0x108 0x370 0x00 0x02 0x00 0x1d0 0xfc 0x364 0x00 0x02 0x00 0x190>; + fsl,pins = <0x138 0x3a0 0x00 0x12 0x00 0x190 0x13c 0x3a4 0x00 0x02 0x00 0x1d0 0x11c 0x384 0x00 0x02 0x00 0x1d0 0x120 0x388 0x00 0x02 0x00 0x1d0 0x124 0x38c 0x00 0x02 0x00 0x1d0 0x128 0x390 0x00 0x02 0x00 0x1d0 0x130 0x398 0x00 0x02 0x00 0x1d0 0x100 0x368 0x00 0x02 0x00 0x1d0 0x104 0x36c 0x00 0x02 0x00 0x1d0 0x108 0x370 0x00 0x02 0x00 0x1d0 0xfc 0x364 0x00 0x02 0x00 0x190>; phandle = <0x3c>; }; diff --git a/examples/pinctrl/Makefile b/examples/pinctrl/Makefile new file mode 100644 index 000000000..a984aa990 --- /dev/null +++ b/examples/pinctrl/Makefile @@ -0,0 +1,32 @@ +# +# Copyright 2025, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif +export MICROKIT_BOARD +BUILD_DIR ?= build +export SDDF=$(abspath ../..) +export BUILD_DIR:=$(abspath ${BUILD_DIR}) +export override MICROKIT_SDK:=$(abspath ${MICROKIT_SDK}) + +IMAGE_FILE:= ${BUILD_DIR}/loader.img +REPORT_FILE:= ${BUILD_DIR}/report.txt + +all: ${IMAGE_FILE} + +${IMAGE_FILE} ${REPORT_FILE} clean clobber: ${BUILD_DIR}/Makefile FORCE + ${MAKE} -C ${BUILD_DIR} MICROKIT_SDK=${MICROKIT_SDK} $(notdir $@) + +${BUILD_DIR}/Makefile: pinctrl.mk + mkdir -p ${BUILD_DIR} + cp pinctrl.mk $@ + +FORCE: diff --git a/examples/pinctrl/README.md b/examples/pinctrl/README.md new file mode 100644 index 000000000..f1c30bca6 --- /dev/null +++ b/examples/pinctrl/README.md @@ -0,0 +1,25 @@ + + +# Pinctrl example + +This is a copy of the serial example with pinctrl incoporated to demonstrate how the pinctrl +subsystem can be minimally incorporated. + +There's a pinctrl driver running at the highest priority to program all the registers value read +from the device tree before any other device drivers (at a lower priority) can run. + +## Building + +The following platforms are supported: +* imx8mm_evk +* imx8mp_evk +* imx8mq_evk +* maaxboard + +For further details on building and running this example, please see the serial example. + +For further details on the pinctrl driver, please see [pinctrl.md](../../docs/pinctrl/pinctrl.md) diff --git a/examples/pinctrl/meta.py b/examples/pinctrl/meta.py new file mode 100644 index 000000000..8b2772c2c --- /dev/null +++ b/examples/pinctrl/meta.py @@ -0,0 +1,134 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import argparse +from typing import List +from dataclasses import dataclass +from sdfgen import SystemDescription, Sddf, DeviceTree +from importlib.metadata import version + +# @billn fix +# assert version('sdfgen').split(".")[1] == "24", "Unexpected sdfgen version" + +ProtectionDomain = SystemDescription.ProtectionDomain + + +@dataclass +class RegRegion: + start_paddr: int + size: int + + +@dataclass +class Board: + name: str + arch: SystemDescription.Arch + paddr_top: int + serial: str + pinctrl: str + + +BOARDS: List[Board] = [ + Board( + name="maaxboard", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/serial@30860000", + pinctrl="soc@0/bus@30000000/iomuxc@30330000", + ), + Board( + name="imx8mm_evk", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/spba-bus@30800000/serial@30890000", + pinctrl="soc@0/bus@30000000/pinctrl@30330000", + ), + Board( + name="imx8mp_evk", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/spba-bus@30800000/serial@30890000", + pinctrl="soc@0/bus@30000000/pinctrl@30330000", + ), + Board( + name="imx8mq_evk", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/serial@30860000", + pinctrl="soc@0/bus@30000000/pinctrl@30330000", + ), +] + + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): + pinctrl_node = dtb.node(board.pinctrl) + assert pinctrl_node is not None + + # Ensure the priority is exclusively the highest as the pinctrl driver must run first! + # This is enforced by sdfgen at the render() step. + pinctrl_driver = ProtectionDomain( + "pinctrl_driver", "pinctrl_driver.elf", priority=253 + ) + pinctrl_system = Sddf.Pinctrl(sdf, pinctrl_node, pinctrl_driver) + + serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) + # Increase the stack size as running with UBSAN uses more stack space than normal. + serial_virt_tx = ProtectionDomain( + "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 + ) + serial_virt_rx = ProtectionDomain( + "serial_virt_rx", "serial_virt_rx.elf", priority=199, stack_size=0x2000 + ) + serial_client = ProtectionDomain("serial_client", "serial_client.elf", priority=1) + + serial_node = dtb.node(board.serial) + assert serial_node is not None + + serial_system = Sddf.Serial( + sdf, + serial_node, + serial_driver, + serial_virt_tx, + virt_rx=serial_virt_rx, + enable_color=False, + ) + serial_system.add_client(serial_client) + + pds = [ + pinctrl_driver, + serial_driver, + serial_virt_tx, + serial_virt_rx, + serial_client, + ] + for pd in pds: + sdf.add_pd(pd) + + assert serial_system.connect() + assert serial_system.serialise_config(output_dir) + + assert pinctrl_system.connect() + assert pinctrl_system.serialise_config(output_dir) + + with open(f"{output_dir}/{sdf_file}", "w+") as f: + f.write(sdf.render()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dtb", required=True) + parser.add_argument("--sddf", required=True) + parser.add_argument("--board", required=True, choices=[b.name for b in BOARDS]) + parser.add_argument("--output", required=True) + parser.add_argument("--sdf", required=True) + + args = parser.parse_args() + + board = next(filter(lambda b: b.name == args.board, BOARDS)) + + sdf = SystemDescription(board.arch, board.paddr_top) + sddf = Sddf(args.sddf) + + with open(args.dtb, "rb") as f: + dtb = DeviceTree(f.read()) + + generate(args.sdf, args.output, dtb) diff --git a/examples/pinctrl/pinctrl.mk b/examples/pinctrl/pinctrl.mk new file mode 100644 index 000000000..675102752 --- /dev/null +++ b/examples/pinctrl/pinctrl.mk @@ -0,0 +1,121 @@ +# +# Copyright 2023, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# This Makefile is copied into the build directory +# and operated on from there. +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(SDDF)),) +$(error SDDF must be specified) +endif + +BUILD_DIR ?= build +MICROKIT_CONFIG ?= debug +IMAGE_FILE = loader.img +REPORT_FILE = report.txt + +CC := clang +LD := ld.lld +AS := clang +AR := llvm-ar +RANLIB := llvm-ranlib +OBJCOPY := llvm-objcopy +DTC := dtc +PYTHON ?= python3 + +MICROKIT_TOOL := $(MICROKIT_SDK)/bin/microkit + +ifneq ($(filter $(strip $(MICROKIT_BOARD)),imx8mm_evk imx8mp_evk imx8mq_evk maaxboard),) + DRIVER_DIR := imx + CPU := cortex-a53 +else +$(error Unsupported MICROKIT_BOARD given) +endif + +TOP := ${SDDF}/examples/pinctrl +METAPROGRAM := $(TOP)/meta.py +UTIL := $(SDDF)/util +PINCTRL_DRIVER := $(SDDF)/drivers/pinctrl/$(DRIVER_DIR) +UART_DRIVER := $(SDDF)/drivers/serial/$(DRIVER_DIR) +SERIAL_COMPONENTS := $(SDDF)/serial/components +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) +SYSTEM_FILE := pinctrl.system +DTS := $(SDDF)/dts/$(MICROKIT_BOARD).dts +DTB := $(MICROKIT_BOARD).dtb +ARCH := ${shell grep 'CONFIG_SEL4_ARCH ' $(BOARD_DIR)/include/kernel/gen_config.h | cut -d' ' -f4} +SDDF_CUSTOM_LIBC := 1 +QEMU := qemu-system-$(ARCH) + +IMAGES := pinctrl_driver.elf serial_driver.elf \ + serial_client.elf \ + serial_virt_tx.elf serial_virt_rx.elf +CFLAGS := -ffreestanding \ + -g3 -O3 -Wall \ + -Wno-unused-function -Werror \ + -MD +LDFLAGS := -L$(BOARD_DIR)/lib -L$(SDDF)/lib +LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group + +ifeq ($(ARCH),aarch64) + CFLAGS += -mcpu=$(CPU) -mstrict-align -target aarch64-none-elf + ASFLAGS := -target aarch64-none-elf +else ifeq ($(ARCH),riscv64) + CFLAGS += -march=rv64imafdc -target riscv64-none-elf + ASFLAGS := -target riscv64-none-elf +endif +CFLAGS += -I$(BOARD_DIR)/include \ + -I${TOP}/include \ + -I${SDDF}/include \ + -I${SDDF}/include/microkit \ + $(CFLAGS_ARCH) + +CHECK_FLAGS_BOARD_MD5:=.board_cflags-$(shell echo -- ${CFLAGS} ${MICROKIT_SDK} ${MICROKIT_BOARD} ${MICROKIT_CONFIG} | shasum | sed 's/ *-//') + +${CHECK_FLAGS_BOARD_MD5}: + -rm -f .board_cflags-* + touch $@ + +${IMAGES}: libsddf_util_debug.a ${CHECK_FLAGS_BOARD_MD5} + +include ${SDDF}/util/util.mk +include ${PINCTRL_DRIVER}/pinctrl_driver.mk +include ${UART_DRIVER}/serial_driver.mk +include ${SERIAL_COMPONENTS}/serial_components.mk + +%.elf: %.o + ${LD} -o $@ ${LDFLAGS} $< ${LIBS} + +serial_client.elf: serial_client.o libsddf_util.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial_client.o: ${TOP}/serial_client.c ${CHECK_FLAGS_BOARD_MD5} + $(CC) $(CFLAGS) -c -o $@ $< + +$(DTB): $(DTS) + dtc -q -I dts -O dtb $(DTS) > $(DTB) + +$(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) + $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) + $(OBJCOPY) --update-section .device_resources=pinctrl_driver_device_resources.data pinctrl_driver.elf + + $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_rx_config=serial_virt_rx.data serial_virt_rx.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=serial_client_serial_client.data serial_client.elf + +$(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) + MICROKIT_SDK=${MICROKIT_SDK} $(MICROKIT_TOOL) $(SYSTEM_FILE) --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) + +clean:: + ${RM} -f *.elf + find . -name '*.[od]' | xargs ${RM} -f + +clobber:: clean + ${RM} -f ${IMAGE_FILE} ${REPORT_FILE} diff --git a/examples/pinctrl/serial_client.c b/examples/pinctrl/serial_client.c new file mode 100644 index 000000000..bfbdf3d71 --- /dev/null +++ b/examples/pinctrl/serial_client.c @@ -0,0 +1,45 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +__attribute__((__section__(".serial_client_config"))) serial_client_config_t config; + +serial_queue_handle_t rx_queue_handle; +serial_queue_handle_t tx_queue_handle; + +uint32_t local_head; + +void init(void) +{ + assert(serial_config_check_magic(&config)); + + serial_queue_init(&rx_queue_handle, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); + serial_queue_init(&tx_queue_handle, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); + + serial_putchar_init(config.tx.id, &tx_queue_handle); + sddf_printf("Hello world! I am %s.\nPlease give me character!\n", microkit_name); +} + +uint16_t char_count; +void notified(microkit_channel ch) +{ + char c; + while (!serial_dequeue(&rx_queue_handle, &c)) { + if (c == '\r') { + sddf_putchar_unbuffered('\\'); + sddf_putchar_unbuffered('r'); + } else { + sddf_putchar_unbuffered(c); + } + char_count++; + if (char_count % 10 == 0) { + sddf_printf("\n%s has received %u characters so far!\n", microkit_name, char_count); + } + } +}