blob: 3c4bf0e59db1e493324eb1825fe9438b9c1fa8ff [file] [log] [blame]
.. _pinctrl-guide:
Pin Control
###########
This is a high-level guide to pin control. See :ref:`pinctrl_api` for API
reference material.
Introduction
************
The hardware blocks that control pin multiplexing and pin configuration
parameters such as pin direction, pull-up/down resistors, etc. are named **pin
controllers**. The pin controller's main users are SoC hardware peripherals,
since the controller enables exposing peripheral signals, like for example,
map ``I2C0`` ``SDA`` signal to pin ``PX0``. Not only that, but it usually allows
configuring certain pin settings that are necessary for the correct functioning
of a peripheral, for example, the slew-rate depending on the operating
frequency. The available configuration options are vendor/SoC dependent and can
range from simple pull-up/down options to more advanced settings such as
debouncing, low-power modes, etc.
The way pin control is implemented in hardware is vendor/SoC specific. It is
common to find a *centralized* approach, that is, all pin configuration
parameters are controlled by a single hardware block (typically named pinmux),
including signal mapping. :numref:`pinctrl-hw-cent-control` illustrates this
approach. ``PX0`` can be mapped to ``UART0_TX``, ``I2C0_SCK`` or ``SPI0_MOSI``
depending on the ``AF`` control bits. Other configuration parameters such as
pull-up/down are controlled in the same block via ``CONFIG`` bits. This model is
used by several SoC families, such as many from NXP and STM32.
.. _pinctrl-hw-cent-control:
.. figure:: images/hw-cent-control.svg
Example of pin control centralized into a single per-pin block
Other vendors/SoCs use a *distributed* approach. In such case, the pin mapping
and configuration are controlled by multiple hardware blocks.
:numref:`pinctrl-hw-dist-control` illustrates a distributed approach where pin
mapping is controlled by peripherals, such as in Nordic nRF SoCs.
.. _pinctrl-hw-dist-control:
.. figure:: images/hw-dist-control.svg
Example pin control distributed between peripheral registers and per-pin block
From a user perspective, there is no difference in pin controller usage
regardless of the hardware implementation: a user will always apply a state.
The only difference lies in the driver implementation. In general, implementing
a pin controller driver for a hardware that uses a distributed approach requires
more effort, since the driver needs to gather knowledge of peripheral dependent
registers.
Pin control vs. GPIO
====================
Some functionality covered by a pin controller driver overlaps with GPIO
drivers. For example, pull-up/down resistors can usually be enabled by both the
pin control driver and the GPIO driver. In Zephyr context, the pin control
driver purpose is to perform peripheral signal multiplexing and configuration of
other pin parameters required for the correct operation of that peripheral.
Therefore, the main users of the pin control driver are SoC peripherals. In
contrast, GPIO drivers are for general purpose control of a pin, that is, when
its logic level is read or controlled manually.
State model
***********
For a device driver to operate correctly, a certain pin configuration needs to
be applied. Some device drivers require a static configuration, usually set up
at initialization time. Others need to change the configuration at runtime
depending on the operating conditions, for example, to enable a low-power mode
when suspending the device. Such requirements are modeled using **states**, a
concept that has been adapted from the one in the Linux kernel. Each device
driver owns a set of states. Each state has a unique name and contains a full
pin configuration set (see :numref:`pinctrl-states-model`). This effectively
means that states are independent of each other, so they do not need to be
applied in any specific order. Another advantage of the state model is that it
isolates device drivers from pin configuration.
.. _pinctrl-states-model:
.. table:: Example pin configuration encoded using the states model
:align: center
+----+------------------+----+------------------+
| ``UART0`` peripheral |
+====+==================+====+==================+
| ``default`` state | ``sleep`` state |
+----+------------------+----+------------------+
| TX | - Pin: PA0 | TX | - Pin: PA0 |
| | - Pull: NONE | | - Pull: NONE |
| | - Low Power: NO | | - Low Power: YES |
+----+------------------+----+------------------+
| RX | - Pin: PA1 | RX | - Pin: PA1 |
| | - Pull: UP | | - Pull: NONE |
| | - Low Power: NO | | - Low Power: YES |
+----+------------------+----+------------------+
Standard states
===============
The name assigned to pin control states or the number of them is up to the
device driver requirements. In many cases a single state applied at
initialization time will be sufficient, but in some other cases more will be
required. In order to make things consistent, a naming convention has been
established for the most common use cases. :numref:`pinctrl-states-standard`
details the standardized states and its purpose.
.. _pinctrl-states-standard:
.. table:: Standardized state names
:align: center
+-------------+----------------------------------+-------------------------+
| State | Identifier | Purpose |
+-------------+----------------------------------+-------------------------+
| ``default`` | :c:macro:`PINCTRL_STATE_DEFAULT` | State of the pins when |
| | | the device is in |
| | | operational state |
+-------------+----------------------------------+-------------------------+
| ``sleep`` | :c:macro:`PINCTRL_STATE_SLEEP` | State of the pins when |
| | | the device is in low |
| | | power or sleep modes |
+-------------+----------------------------------+-------------------------+
Note that other standard states could be introduced in the future.
Custom states
=============
Some device drivers may require using custom states beyond the standard ones. To
achieve that, the device driver needs to have in its scope definitions for the
custom state identifiers named as ``PINCTRL_STATE_{STATE_NAME}``, where
``{STATE_NAME}`` is the capitalized state name. For example, if ``mystate`` has
to be supported, a definition named ``PINCTRL_STATE_MYSTATE`` needs to be
in the driver's scope.
.. note::
It is important that custom state identifiers start from
:c:macro:`PINCTRL_STATE_PRIV_START`
If custom states need to be accessed from outside the driver, for example to
perform dynamic pin control, custom identifiers should be placed in a header
that is publicly accessible.
Skipping states
===============
In most situations, the states defined in Devicetree will be the ones used in
the compiled firmware. However, there are some cases where certain states will
be conditionally used depending on a compilation flag. A typical case is the
``sleep`` state. This state is only used in practice if
:kconfig:option:`CONFIG_PM_DEVICE` is enabled. If a firmware variant without device
power management is needed, one should in theory remove the ``sleep`` state from
Devicetree to not waste ROM space storing such unused state.
States can be skipped by the ``pinctrl`` Devicetree macros if a definition named
``PINCTRL_SKIP_{STATE_NAME}`` expanding to ``1`` is present when pin control
configuration is defined. In case of the ``sleep`` state, the ``pinctrl`` API
already provides such definition conditional to the availability of device power
management:
.. code-block:: c
#ifndef CONFIG_PM_DEVICE
/** If device power management is not enabled, "sleep" state will be ignored. */
#define PINCTRL_SKIP_SLEEP 1
#endif
Dynamic pin control
*******************
Dynamic pin control refers to the capability of changing pin configuration
at runtime. This feature can be useful in situations where the same firmware
needs to run onto slightly different boards, each having a peripheral routed at
a different set of pins. This feature can be enabled by setting
:kconfig:option:`CONFIG_PINCTRL_DYNAMIC`.
.. note::
Dynamic pin control should only be used on devices that have not been
initialized. Changing pin configurations while a device is operating may
lead to unexpected behavior. Since Zephyr does not support device
de-initialization yet, this functionality should only be used during early
boot stages.
One of the effects of enabling dynamic pin control is that
:c:struct:`pinctrl_dev_config` will be stored in RAM instead of ROM (not states
or pin configurations, though). The user can then use
:c:func:`pinctrl_update_states` to update the states stored in
:c:struct:`pinctrl_dev_config` with a new set. This effectively means that the
device driver will apply the pin configurations stored in the updated states
when it applies a state.
Devicetree representation
*************************
Because Devicetree is meant to describe hardware, it is the natural choice when
it comes to storing pin control configuration. In the following sections you
will find an overview on how states and pin configurations are represented in
Devicetree.
States
======
Given a device, each of its pin control state is represented in Devicetree by
``pinctrl-N`` properties, being ``N`` the state index starting from zero. The
``pinctrl-names`` property is then used to assign a unique identifier for each
state property by index, for example, ``pinctrl-names`` list entry 0 is the name
for ``pinctrl-0``.
.. code-block:: devicetree
periph0: periph@0 {
...
/* state 0 ("default") */
pinctrl-0 = <...>;
...
/* state N ("mystate") */
pinctrl-N = <...>;
/* names for state 0 up to state N */
pinctrl-names = "default", ..., "mystate";
...
};
Pin configuration
=================
There are multiple ways to represent the pin configurations in Devicetree.
However, all end up encoding the same information: the pin multiplexing and the
pin configuration parameters. For example, ``UART_RX`` is mapped to ``PX0`` and
pull-up is enabled. The representation choice largely depends on each
vendor/SoC, so the Devicetree binding files for the pin control drivers are the
best place to look for details.
A popular and versatile option is shown in the example below. One of the
advantages of this choice is the grouping capability based on shared pin
configuration. This allows to reduce the verbosity of the pin control
definitions. Another advantage is that the pin configuration parameters for a
particular state are enclosed in a single Devicetree node.
.. code-block:: devicetree
/* board.dts */
#include "board-pinctrl.dtsi"
&periph0 {
pinctrl-0 = <&periph0_default>;
pinctrl-names = "default";
};
.. code-block:: c
/* vnd-soc-pkgxx.h
* File with valid mappings for a specific package (may be autogenerated).
* This file is optional, but recommended.
*/
...
#define PERIPH0_SIGA_PX0 VNDSOC_PIN(X, 0, MUX0)
#define PERIPH0_SIGB_PY7 VNDSOC_PIN(Y, 7, MUX4)
#define PERIPH0_SIGC_PZ1 VNDSOC_PIN(Z, 1, MUX2)
...
.. code-block:: devicetree
/* board-pinctrl.dtsi */
#include <vnd-soc-pkgxx.h>
&pinctrl {
/* Node with pin configuration for default state */
periph0_default: periph0_default {
group1 {
/* Mappings: PERIPH0_SIGA -> PX0, PERIPH0_SIGC -> PZ1 */
pinmux = <PERIPH0_SIGA_PX0>, <PERIPH0_SIGC_PZ1>;
/* Pins PX0 and PZ1 have pull-up enabled */
bias-pull-up;
};
...
groupN {
/* Mappings: PERIPH0_SIGB -> PY7 */
pinmux = <PERIPH0_SIGB_PY7>;
};
};
};
Another popular model is based on having a node for each pin configuration and
state. While this model may lead to shorter board pin control files, it also
requires to have one node for each pin mapping and state, since in general,
nodes can not be re-used for multiple states. This method is discouraged if
autogeneration is not an option.
.. note::
Because all Devicetree information is parsed into a C header, it is important
to make sure its size is kept to a minimum. For this reason it is important
to prefix pre-generated nodes with ``/omit-if-no-ref/``. This prefix makes
sure that the node is discarded when not used.
.. code-block:: devicetree
/* board.dts */
#include "board-pinctrl.dtsi"
&periph0 {
pinctrl-0 = <&periph0_siga_px0_default &periph0_sigb_py7_default
&periph0_sigc_pz1_default>;
pinctrl-names = "default";
};
.. code-block:: devicetree
/* vnd-soc-pkgxx.dtsi
* File with valid nodes for a specific package (may be autogenerated).
* This file is optional, but recommended.
*/
&pinctrl {
/* Mapping for PERIPH0_SIGA -> PX0, to be used for default state */
/omit-if-no-ref/ periph0_siga_px0_default: periph0_siga_px0_default {
pinmux = <VNDSOC_PIN(X, 0, MUX0)>;
};
/* Mapping for PERIPH0_SIGB -> PY7, to be used for default state */
/omit-if-no-ref/ periph0_sigb_py7_default: periph0_sigb_py7_default {
pinmux = <VNDSOC_PIN(Y, 7, MUX4)>;
};
/* Mapping for PERIPH0_SIGC -> PZ1, to be used for default state */
/omit-if-no-ref/ periph0_sigc_pz1_default: periph0_sigc_pz1_default {
pinmux = <VNDSOC_PIN(Z, 1, MUX2)>;
};
};
.. code-block:: devicetree
/* board-pinctrl.dts */
#include <vnd-soc-pkgxx.dtsi>
/* Enable pull-up for PX0 (default state) */
&periph0_siga_px0_default {
bias-pull-up;
};
/* Enable pull-up for PZ1 (default state) */
&periph0_sigc_pz1_default {
bias-pull-up;
};
.. note::
It is discouraged to add pin configuration defaults in pre-defined nodes.
In general, pin configurations depend on the board design or on the
peripheral working conditions, so the decision should be made by the board.
For example, enabling a pull-up by default may not always be desired because
the board already has one or because its value depends on the operating bus
speed. Another downside of defaults is that user may not be aware of them,
for example:
.. code-block:: devicetree
/* not evident that "periph0_siga_px0_default" also implies "bias-pull-up" */
/omit-if-no-ref/ periph0_siga_px0_default: periph0_siga_px0_default {
pinmux = <VNDSOC_PIN(X, 0, MUX0)>;
bias-pull-up;
};
Implementation guidelines
*************************
Pin control drivers
===================
Pin control drivers need to implement a single function:
:c:func:`pinctrl_configure_pins`. This function receives an array of pin
configurations that need to be applied. Furthermore, if
:kconfig:option:`CONFIG_PINCTRL_STORE_REG` is set, it also receives the associated
device register address for the given pins. This information may be required by
some drivers to perform device specific actions.
The pin configuration is stored in an opaque type that is vendor/SoC dependent:
``pinctrl_soc_pin_t``. This type needs to be defined in a header named
``pinctrl_soc.h`` file that is in the Zephyr's include path. It can range from
a simple integer value to a struct with multiple fields. ``pinctrl_soc.h`` also
needs to define a macro named ``Z_PINCTRL_STATE_PINS_INIT`` that accepts two
arguments: a node identifier and a property name (``pinctrl-N``). With this
information the macro needs to define an initializer for all pin configurations
contained within the ``pinctrl-N`` property of the given node.
Regarding Devicetree pin configuration representation, vendors can decide which
option is better for their devices. However, the following guidelines should be
followed:
- Use ``pinctrl-N`` (N=0, 1, ...) and ``pinctrl-names`` properties to define pin
control states. These properties are defined in
:file:`dts/bindings/pinctrl/pinctrl-device.yaml`.
- Use standard pin configuration properties as defined in
:file:`dts/bindings/pinctrl/pincfg-node.yaml`.
Representations not following these guidelines may be accepted if they are
already used by the same vendor in other operating systems, e.g. Linux.
Device drivers
==============
In this section you will find some tips on how a device driver should use the
``pinctrl`` API to successfully configure the pins it needs.
The device compatible needs to be modified in the corresponding binding so that
the ``pinctrl-device.yaml`` is included. For example:
.. code-block:: yaml
include: [base.yaml, pinctrl-device.yaml]
This file is needed to add ``pinctrl-N`` and ``pinctrl-names`` properties to the
device.
From a device driver perspective there are two steps that need to be performed
to be able to use the ``pinctrl`` API. First, the pin control configuration
needs to be defined. This includes all states and pins.
:c:macro:`PINCTRL_DT_DEFINE` or :c:macro:`PINCTRL_DT_INST_DEFINE` macros
should be used for this purpose. Second, a reference to
the device instance :c:struct:`pinctrl_dev_config` needs to be stored, since it
is required to later use the API. This can be achieved using the
:c:macro:`PINCTRL_DT_DEV_CONFIG_GET` and
:c:macro:`PINCTRL_DT_INST_DEV_CONFIG_GET` macros.
It is worth to note that the only relationship between a device and its
associated pin control configuration is based on variable naming conventions.
The way an instance of :c:struct:`pinctrl_dev_config` is named for a
corresponding device instance allows to later obtain a reference to it given the
device's Devicetree node identifier. This allows to minimize ROM usage, since
only devices requiring pin control will own a reference to a pin control
configuration.
Once the driver has defined the pin control configuration and kept a reference
to it, it is ready to use the API. The most common way to apply a state is by
using :c:func:`pinctrl_apply_state`. It is also possible to use the lower level
function :c:func:`pinctrl_apply_state_direct` to skip state lookup if it is
cached in advance (e.g. at init time). Since state lookup time is expected to be
fast, it is recommended to use :c:func:`pinctrl_apply_state`.
The example below contains a complete example of a device driver that uses the
``pinctrl`` API.
.. code-block:: c
/* A driver for the "mydev" compatible device */
#define DT_DRV_COMPAT mydev
...
#include <zephyr/drivers/pinctrl.h>
...
struct mydev_config {
...
/* Reference to mydev pinctrl configuration */
const struct pinctrl_dev_config *pcfg;
...
};
...
static int mydev_init(const struct device *dev)
{
const struct mydev_config *config = dev->config;
int ret;
...
/* Select "default" state at initialization time */
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (ret < 0) {
return ret;
}
...
}
#define MYDEV_DEFINE(i) \
/* Define all pinctrl configuration for instance "i" */ \
PINCTRL_DT_INST_DEFINE(i); \
... \
static const struct mydev_config mydev_config_##i = { \
... \
/* Keep a ref. to the pinctrl configuration for instance "i" */ \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i), \
... \
}; \
... \
\
DEVICE_DT_INST_DEFINE(i, mydev_init, NULL, &mydev_data##i, \
&mydev_config##i, ...);
DT_INST_FOREACH_STATUS_OKAY(MYDEV_DEFINE)
.. _pinctrl_api:
Pin Control API
****************
.. doxygengroup:: pinctrl_interface
Dynamic pin control
====================
.. doxygengroup:: pinctrl_interface_dynamic
Other reference material
************************
- `Introduction to pin muxing and GPIO control under Linux <https://static.sched.com/hosted_files/osselc21/b6/ELC-2021_Introduction_to_pin_muxing_and_GPIO_control_under_Linux.pdf>`_