Skip to content
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
1 change: 1 addition & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
35 changes: 35 additions & 0 deletions ci/examples/pinctrl.py
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 17 additions & 0 deletions ci/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions docs/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions docs/pinctrl/imx/imx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!--
Copyright 2025, UNSW
SPDX-License-Identifier: CC-BY-SA-4.0
-->

# 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.
139 changes: 139 additions & 0 deletions docs/pinctrl/pinctrl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<!--
Copyright 2025, UNSW
SPDX-License-Identifier: CC-BY-SA-4.0
-->

# 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
<!-- This paragraph was paraphrased from two documents:
Linux pinctrl documentation: https://www.kernel.org/doc/Documentation/pinctrl.txt
i.MX 8 Chapter 8 Chip IO and Pinmux:
https://community.nxp.com/pwmxy87654/attachments/pwmxy87654/imx-processors/213251/1/IMX8MPRM-TableMuxing.pdf -->
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.
18 changes: 18 additions & 0 deletions drivers/pinctrl/imx/config.json
Original file line number Diff line number Diff line change
@@ -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": []
}
}
Loading
Loading