507 lines
20 KiB
ReStructuredText
507 lines
20 KiB
ReStructuredText
.. _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. The figure below 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.
|
|
|
|
.. 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.
|
|
The figure below illustrates a distributed approach where pin
|
|
mapping is controlled by peripherals, such as in Nordic nRF SoCs.
|
|
|
|
.. 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 the figure below). 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.
|
|
|
|
.. 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. The figure below
|
|
details the standardized states and its purpose.
|
|
|
|
.. 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` or :kconfig:option:`CONFIG_PM_DEVICE` is enabled.
|
|
If a firmware variant without these power management configurations 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
|
|
|
|
#if !defined(CONFIG_PM) && !defined(CONFIG_PM_DEVICE)
|
|
/** Out of power management configurations, ignore "sleep" state. */
|
|
#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://elinux.org/images/a/a7/ELC-2021_Introduction_to_pin_muxing_and_GPIO_control_under_Linux.pdf>`_
|