diff --git a/CODEOWNERS b/CODEOWNERS index 4fd2ded37cdd9..3f67468ac864c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -290,6 +290,7 @@ /drivers/modem/Kconfig.hl7800 @LairdCP/zephyr /drivers/pcie/ @dcpleung @nashif @jhedberg /drivers/peci/ @albertofloyd @franciscomunoz @scottwcpg +/drivers/pinctrl/ @gmarull /drivers/pinmux/*b91* @yurvyn /drivers/pinmux/*hsdk* @iriszzw /drivers/pinmux/*it8xxx2* @ite diff --git a/doc/conf.py b/doc/conf.py index 9110effe5034f..867d6466e9d0f 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -108,6 +108,8 @@ todo_include_todos = False +numfig = True + rst_epilog = """ .. include:: /substitutions.txt """ diff --git a/doc/guides/index.rst b/doc/guides/index.rst index 333110c67664e..e101f850981ae 100644 --- a/doc/guides/index.rst +++ b/doc/guides/index.rst @@ -17,6 +17,7 @@ User and Developer Guides debug_tools/index device_mgmt/index dts/index + pinctrl/index emulator/index.rst modules.rst diff --git a/doc/guides/pinctrl/images/hw-cent-control.odg b/doc/guides/pinctrl/images/hw-cent-control.odg new file mode 100644 index 0000000000000..fd979500665c4 Binary files /dev/null and b/doc/guides/pinctrl/images/hw-cent-control.odg differ diff --git a/doc/guides/pinctrl/images/hw-cent-control.svg b/doc/guides/pinctrl/images/hw-cent-control.svg new file mode 100644 index 0000000000000..571e90f134192 --- /dev/null +++ b/doc/guides/pinctrl/images/hw-cent-control.svg @@ -0,0 +1,905 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PX0 Control + + + + + + Alternate + + + + + + + + PX0 + + + + + + Function + + + + + + TX + + + + + + + + + + + + + + + + + + + + + + + + + + + I2C1 + + + + + + SCK + + + + + + MOSI + + + + + + SPI0 + + + + + + UART0 + + + + + + + + AF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CONFIG + + + + + + + + STRENGTH + + + + + + + + DRIVE + + + + + + + + \ No newline at end of file diff --git a/doc/guides/pinctrl/images/hw-dist-control.odg b/doc/guides/pinctrl/images/hw-dist-control.odg new file mode 100644 index 0000000000000..25b6c7ae0f122 Binary files /dev/null and b/doc/guides/pinctrl/images/hw-dist-control.odg differ diff --git a/doc/guides/pinctrl/images/hw-dist-control.svg b/doc/guides/pinctrl/images/hw-dist-control.svg new file mode 100644 index 0000000000000..0030d75fc9933 --- /dev/null +++ b/doc/guides/pinctrl/images/hw-dist-control.svg @@ -0,0 +1,1198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PX0 Control + + + + + + + + PX0 + + + + + + + + I2C1_SCK_PIN + + + + + + + + I2C1_SDA_PIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + I2C1_SDA_PIN == PX0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ... + + + + + + + + + + + + + + + UART0_RX_PIN + + + + + + + + UART0_TX_PIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UART0_RX_PIN == PX0 + + + + + + + + + + + + + + + + + + + + + + I2C1_SCK_PIN == PX0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UART0_TX_PIN == PX0 + + + + + + SDA + + + + + + SCK + + + + + + TX + + + + + + RX + + + + + + + + ... + + + + + + I2C1 + + + + + + UART0 + + + + + + + + CONFIG + + + + + + + + + + + + + + + + + + + + + STRENGTH + + + + + + + + DRIVE + + + + + + + + \ No newline at end of file diff --git a/doc/guides/pinctrl/index.rst b/doc/guides/pinctrl/index.rst new file mode 100644 index 0000000000000..40e80235a65b0 --- /dev/null +++ b/doc/guides/pinctrl/index.rst @@ -0,0 +1,492 @@ +.. _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:`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:`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 + + &pinctrl { + /* Node with pin configuration for default state */ + periph0_default: periph0_default { + pins1 { + /* Mappings: PERIPH0_SIGA -> PX0, PERIPH0_SIGC -> PZ1 */ + pinmux = , ; + /* Pins PX0 and PZ1 have pull-up enabled */ + bias-pull-up; + }; + ... + pinsN { + /* Mappings: PERIPH0_SIGB -> PY7 */ + pinmux = ; + }; + }; + }; + +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. + +.. 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. + */ + + /* Mapping for PERIPH0_SIGA -> PX0, to be used for default state */ + periph0_siga_px0_default: periph0_siga_px0_default { + pinmux = ; + }; + + /* Mapping for PERIPH0_SIGB -> PY7, to be used for default state */ + periph0_sigb_py7_default: periph0_sigb_py7_default { + pinmux = ; + }; + + /* Mapping for PERIPH0_SIGC -> PZ1, to be used for default state */ + periph0_sigc_pz1_default: periph0_sigc_pz1_default { + pinmux = ; + }; + +.. code-block:: devicetree + + /* board-pinctrl.dts */ + #include + + /* 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" */ + periph0_siga_px0_default: periph0_siga_px0_default { + pinmux = ; + 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:`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` or + :file:`dts/bindings/pinctrl/pincfg-node-group.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 + ... + + 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) + +Other reference material +************************ + +- `Introduction to pin muxing and GPIO control under Linux `_ diff --git a/doc/reference/api/overview.rst b/doc/reference/api/overview.rst index 231e56fea2158..2e61009da78ce 100644 --- a/doc/reference/api/overview.rst +++ b/doc/reference/api/overview.rst @@ -232,6 +232,11 @@ current :ref:`stability level `. - 1.0 - 2.6 + * - :ref: `pinctrl_api` + - Experimental + - 3.0 + - 3.0 + * - :ref:`pinmux_api` - Stable - 1.0 diff --git a/doc/reference/index.rst b/doc/reference/index.rst index d77a8d2078222..28499be390848 100644 --- a/doc/reference/index.rst +++ b/doc/reference/index.rst @@ -27,6 +27,7 @@ API Reference modbus/index.rst networking/index.rst peripherals/index.rst + pinctrl/index.rst power_management/index.rst random/index.rst resource_management/index.rst diff --git a/doc/reference/pinctrl/index.rst b/doc/reference/pinctrl/index.rst new file mode 100644 index 0000000000000..cf2a5ab44c808 --- /dev/null +++ b/doc/reference/pinctrl/index.rst @@ -0,0 +1,11 @@ +.. _pinctrl_api: + +Pin Control +########### + +.. doxygengroup:: pinctrl_interface + +Dynamic pin control +******************* + +.. doxygengroup:: pinctrl_interface_dynamic diff --git a/doc/zephyr.doxyfile.in b/doc/zephyr.doxyfile.in index e8a2f993ba866..897ef50f9668d 100644 --- a/doc/zephyr.doxyfile.in +++ b/doc/zephyr.doxyfile.in @@ -2229,7 +2229,8 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = CONFIG_ARCH_HAS_CUSTOM_BUSY_WAIT \ +PREDEFINED = __DOXYGEN__ \ + CONFIG_ARCH_HAS_CUSTOM_BUSY_WAIT \ CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN \ CONFIG_BT_BREDR \ CONFIG_BT_MESH_MODEL_EXTENSIONS \ diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index ce280174dd2df..182060d7391ef 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -61,3 +61,4 @@ add_subdirectory_ifdef(CONFIG_CACHE_MANAGEMENT cache) add_subdirectory_ifdef(CONFIG_SYSCON syscon) add_subdirectory_ifdef(CONFIG_BBRAM bbram) add_subdirectory_ifdef(CONFIG_FPGA fpga) +add_subdirectory_ifdef(CONFIG_PINCTRL pinctrl) diff --git a/drivers/Kconfig b/drivers/Kconfig index e70c0c15e5332..c21e9a2929943 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -123,4 +123,6 @@ source "drivers/bbram/Kconfig" source "drivers/fpga/Kconfig" +source "drivers/pinctrl/Kconfig" + endmenu diff --git a/drivers/pinctrl/CMakeLists.txt b/drivers/pinctrl/CMakeLists.txt new file mode 100644 index 0000000000000..cd3eca95d8d74 --- /dev/null +++ b/drivers/pinctrl/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(common.c) diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig new file mode 100644 index 0000000000000..7e1a67175b84c --- /dev/null +++ b/drivers/pinctrl/Kconfig @@ -0,0 +1,32 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +menuconfig PINCTRL + bool "Enable pin controller drivers" + +if PINCTRL + +config PINCTRL_STORE_REG + bool + help + This option must be selected by drivers that require access to the device + register address. This can happen, for example, if certain pin control + actions are device dependent or require access to device specific + registers + +config PINCTRL_NON_STATIC + bool + help + This option can be selected if the pin control configuration defined by + a driver has to be accessed externally. This can happen, for example, when + dynamic pin control is enabled or in testing environments. + +config PINCTRL_DYNAMIC + bool "Enable dynamic configuration of pins" + select PINCTRL_NON_STATIC + help + When this option is enabled pin control configuration can be changed at + runtime. This can be useful, for example, to change the pins assigned to a + peripheral at early boot stages depending on a certain input. + +endif # PINCTRL diff --git a/drivers/pinctrl/common.c b/drivers/pinctrl/common.c new file mode 100644 index 0000000000000..d2d2652910273 --- /dev/null +++ b/drivers/pinctrl/common.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +int pinctrl_lookup_state(const struct pinctrl_dev_config *config, uint8_t id, + const struct pinctrl_state **state) +{ + *state = &config->states[0]; + while (*state <= &config->states[config->state_cnt - 1U]) { + if (id == (*state)->id) { + return 0; + } + + (*state)++; + } + + return -ENOENT; +} + +#ifdef CONFIG_PINCTRL_DYNAMIC +int pinctrl_update_states(struct pinctrl_dev_config *config, + const struct pinctrl_state *states, + uint8_t state_cnt) +{ + uint8_t equal = 0U; + + /* check we are inserting same number of states */ + if (config->state_cnt != state_cnt) { + return -EINVAL; + } + + /* check we have the same states */ + for (uint8_t i = 0U; i < state_cnt; i++) { + for (uint8_t j = 0U; j < config->state_cnt; j++) { + if (states[i].id == config->states[j].id) { + equal++; + break; + } + } + } + + if (equal != state_cnt) { + return -EINVAL; + } + + /* replace current states */ + config->states = states; + + return 0; +} +#endif /* CONFIG_PINCTRL_DYNAMIC */ diff --git a/dts/bindings/pinctrl/pincfg-node-group.yaml b/dts/bindings/pinctrl/pincfg-node-group.yaml new file mode 100644 index 0000000000000..09ec77d307c13 --- /dev/null +++ b/dts/bindings/pinctrl/pincfg-node-group.yaml @@ -0,0 +1,165 @@ +# Copyright (c) 2020, Linaro Limited +# SPDX-License-Identifier: Apache-2.0 + +description: | + Generic pin configuration schema. + + This file is equivalent to pincfg-node.yaml but inserts properties at a + grandchildren level, useful for group based representation. + +child-binding: + child-binding: + description: | + Many data items that are represented in a pin configuration node are + common and generic. Pin control bindings should use the properties + defined below where they are applicable; not all of these properties are + relevant or useful for all hardware or binding structures. Each + individual binding document should state which of these generic + properties, if any, are used, and the structure of the DT nodes that + contain these properties. + + This is based on Linux, documentation: + https://www.kernel.org/doc/Documentation/devicetree/bindings/pinctrl/pincfg-node.yaml + + properties: + bias-disable: + required: false + type: boolean + description: disable any pin bias + + bias-high-impedance: + required: false + type: boolean + description: high impedance mode ("third-state", "floating") + + bias-bus-hold: + required: false + type: boolean + description: latch weakly + + bias-pull-up: + required: false + type: boolean + description: enable pull-up resistor + + bias-pull-down: + required: false + type: boolean + description: enable pull-down resistor + + bias-pull-pin-default: + required: false + type: boolean + description: use pin's default pull state + + drive-push-pull: + required: false + type: boolean + description: drive actively high and low + + drive-open-drain: + required: false + type: boolean + description: drive with open drain (hardware AND) + + drive-open-source: + required: false + type: boolean + description: drive with open source (hardware OR) + + drive-strength: + required: false + type: int + description: maximum sink or source current in mA + + drive-strength-microamp: + required: false + type: int + description: maximum sink or source current in μA + + input-enable: + required: false + type: boolean + description: | + enable input on pin (no effect on output, such as enabling an input + buffer) + + input-disable: + required: false + type: boolean + description: | + disable input on pin (no effect on output, such as disabling an input + buffer) + + input-schmitt-enable: + required: false + type: boolean + description: enable schmitt-trigger mode + + input-schmitt-disable: + required: false + type: boolean + description: disable schmitt-trigger mode + + input-debounce: + required: false + type: int + description: | + Takes the debounce time in μsec, as argument or 0 to disable debouncing + + power-source: + required: false + type: int + description: select between different power supplies + + low-power-enable: + required: false + type: boolean + description: enable low power mode + + low-power-disable: + required: false + type: boolean + description: disable low power mode + + output-disable: + required: false + type: boolean + description: disable output on a pin (such as disable an output buffer) + + output-enable: + required: false + type: boolean + description: | + enable output on a pin without actively driving it (such as enabling + an output buffer) + + output-low: + required: false + type: boolean + description: set the pin to output mode with low level + + output-high: + required: false + type: boolean + description: set the pin to output mode with high level + + sleep-hardware-state: + required: false + type: boolean + description: | + indicate this is sleep related state which will be programmed into + the registers for the sleep state + + slew-rate: + required: false + type: int + description: set the slew rate + + skew-delay: + required: false + type: int + description: | + This affects the expected clock skew on input pins and the delay + before latching a value to an output pin. Typically indicates how + many double-inverters are used to delay the signal. diff --git a/dts/bindings/pinctrl/pinctrl-device.yaml b/dts/bindings/pinctrl/pinctrl-device.yaml new file mode 100644 index 0000000000000..40679dbac6b88 --- /dev/null +++ b/dts/bindings/pinctrl/pinctrl-device.yaml @@ -0,0 +1,43 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: | + This file needs to be included by devices that need to specify a set of pin + controller states. The maximum number of supported states is 5 (pinctrl-0 ... + pinctrl-4) but it can be incremented if required. + + The bindings file for each pin controller driver implementation should provide + more information on what is the expected pin configuration format. + +properties: + pinctrl-0: + type: phandles + description: | + Pin configuration/s for the first state. Content is specific to the + selected pin controller driver implementation. + + pinctrl-1: + type: phandles + description: | + Pin configuration/s for the second state. See pinctrl-0. + + pinctrl-2: + type: phandles + description: | + Pin configuration/s for the third state. See pinctrl-0. + + pinctrl-3: + type: phandles + description: | + Pin configuration/s for the fourth state. See pinctrl-0. + + pinctrl-4: + type: phandles + description: | + Pin configuration/s for the fifth state. See pinctrl-0. + + pinctrl-names: + type: string-array + description: | + Names for the provided states. The number of names needs to match the + number of states. diff --git a/dts/bindings/test/vnd,pinctrl-device.yaml b/dts/bindings/test/vnd,pinctrl-device.yaml new file mode 100644 index 0000000000000..3f8e18c88b184 --- /dev/null +++ b/dts/bindings/test/vnd,pinctrl-device.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Test device for pin controller. + +compatible: "vnd,pinctrl-device" + +include: [base.yaml, pinctrl-device.yaml] diff --git a/dts/bindings/test/vnd,pinctrl-test.yaml b/dts/bindings/test/vnd,pinctrl-test.yaml new file mode 100644 index 0000000000000..d0c74ac2361e0 --- /dev/null +++ b/dts/bindings/test/vnd,pinctrl-test.yaml @@ -0,0 +1,59 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: | + Test pin controller. + +compatible: "vnd,pinctrl-test" + +include: + - name: base.yaml + - name: pincfg-node-group.yaml + child-binding: + child-binding: + property-allowlist: + - bias-pull-down + - bias-pull-up + +child-binding: + description: | + Test pin controller pin configuration nodes. Each node is composed by one or + more groups, each defining the configuration for a set of pins. + + child-binding: + description: | + Test pin controller pin configuration group. Each group contains a list of + pins sharing the same set of properties. Example: + + /* node representing default state for test_device0 */ + test_device0_default: test_device0_default { + /* group 1 (name is arbitrary) */ + pins1 { + /* configure pins 0 and 1 */ + pins = <0>, <1>; + /* both pins 0 and 1 have pull-up enabled */ + bias-pull-up; + }; + ... + /* group N (name is arbitrary) */ + pinsN { + /* configure pin M */ + pins = ; + /* pin M has pull-down enabled */ + bias-pull-down; + }; + }; + + The list of supported standard properties: + + - bias-pull-up: Enable pull-up resistor. + - bias-pull-down: Enable pull-down resistor. + + properties: + pins: + required: true + type: array + description: | + An array of pins sharing the same group properties. Each entry is a + 32-bit integer that is just used to identify the entry for testing + purposes. diff --git a/include/drivers/pinctrl.h b/include/drivers/pinctrl.h new file mode 100644 index 0000000000000..b8a429c240ab3 --- /dev/null +++ b/include/drivers/pinctrl.h @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * Public APIs for pin control drivers + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_PINCTRL_H_ +#define ZEPHYR_INCLUDE_DRIVERS_PINCTRL_H_ + +/** + * @brief Pin Controller Interface + * @defgroup pinctrl_interface Pin Controller Interface + * @ingroup io_interfaces + * @{ + */ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Pin control states + * @anchor PINCTRL_STATES + * @{ + */ + +/** Default state (state used when the device is in operational state). */ +#define PINCTRL_STATE_DEFAULT 0U +/** Sleep state (state used when the device is in low power mode). */ +#define PINCTRL_STATE_SLEEP 1U + +/** This and higher values refer to custom private states. */ +#define PINCTRL_STATE_PRIV_START 2U + +/** @} */ + +/** Pin control state configuration. */ +struct pinctrl_state { + /** Pin configurations. */ + const pinctrl_soc_pin_t *pins; + /** Number of pin configurations. */ + uint8_t pin_cnt; + /** State identifier (see @ref PINCTRL_STATES). */ + uint8_t id; +}; + +/** Pin controller configuration for a given device. */ +struct pinctrl_dev_config { +#if defined(CONFIG_PINCTRL_STORE_REG) || defined(__DOXYGEN__) + /** + * Device address (only available if @kconfig{CONFIG_PINCTRL_STORE_REG} + * is enabled). + */ + uintptr_t reg; +#endif /* defined(CONFIG_PINCTRL_STORE_REG) || defined(__DOXYGEN__) */ + /** List of state configurations. */ + const struct pinctrl_state *states; + /** Number of state configurations. */ + uint8_t state_cnt; +}; + +/** Utility macro to indicate no register is used. */ +#define PINCTRL_REG_NONE 0U + +/** @cond INTERNAL_HIDDEN */ + +#ifndef CONFIG_PM_DEVICE +/** If device power management is not enabled, "sleep" state will be ignored. */ +#define PINCTRL_SKIP_SLEEP 1 +#endif + +/** + * @brief Obtain the state identifier for the given node and state index. + * + * @param state_idx State index. + * @param node_id Node identifier. + */ +#define Z_PINCTRL_STATE_ID(state_idx, node_id) \ + _CONCAT(PINCTRL_STATE_, \ + DT_PINCTRL_IDX_TO_NAME_UPPER_TOKEN(node_id, state_idx)) + +/** + * @brief Obtain the variable name storing pinctrl config for the given DT node + * identifier. + * + * @param node_id Node identifier. + */ +#define Z_PINCTRL_DEV_CONFIG_NAME(node_id) \ + _CONCAT(__pinctrl_dev_config, DEVICE_DT_NAME_GET(node_id)) + +/** + * @brief Obtain the variable name storing pinctrl states for the given DT node + * identifier. + * + * @param node_id Node identifier. + */ +#define Z_PINCTRL_STATES_NAME(node_id) \ + _CONCAT(__pinctrl_states, DEVICE_DT_NAME_GET(node_id)) + +/** + * @brief Obtain the variable name storing pinctrl pins for the given DT node + * identifier and state index. + * + * @param state_idx State index. + * @param node_id Node identifier. + */ +#define Z_PINCTRL_STATE_PINS_NAME(state_idx, node_id) \ + _CONCAT(__pinctrl_state_pins_ ## state_idx, DEVICE_DT_NAME_GET(node_id)) + +/** + * @brief Utility macro to check if given state has to be skipped. + * + * If a certain state has to be skipped, a macro named PINCTRL_SKIP_ + * can be defined evaluating to 1. This can be useful, for example, to + * automatically ignore the sleep state if no device power management is + * enabled. + * + * @param state_idx State index. + * @param node_id Node identifier. + */ +#define Z_PINCTRL_SKIP_STATE(state_idx, node_id) \ + _CONCAT(PINCTRL_SKIP_, \ + DT_PINCTRL_IDX_TO_NAME_UPPER_TOKEN(node_id, state_idx)) + +/** + * @brief Helper macro to define pins for a given pin control state. + * + * @param state_idx State index. + * @param node_id Node identifier. + */ +#define Z_PINCTRL_STATE_PINS_DEFINE(state_idx, node_id) \ + COND_CODE_1(Z_PINCTRL_SKIP_STATE(state_idx, node_id), (), \ + (static const pinctrl_soc_pin_t \ + Z_PINCTRL_STATE_PINS_NAME(state_idx, node_id)[] = \ + Z_PINCTRL_STATE_PINS_INIT(node_id, pinctrl_ ## state_idx);)) + +/** + * @brief Helper macro to initialize a pin control state. + * + * @param state_idx State index. + * @param node_id Node identifier. + */ +#define Z_PINCTRL_STATE_INIT(state_idx, node_id) \ + COND_CODE_1(Z_PINCTRL_SKIP_STATE(state_idx, node_id), (), \ + ({ \ + .id = Z_PINCTRL_STATE_ID(state_idx, node_id), \ + .pins = Z_PINCTRL_STATE_PINS_NAME(state_idx, node_id), \ + .pin_cnt = ARRAY_SIZE(Z_PINCTRL_STATE_PINS_NAME(state_idx, \ + node_id)) \ + },)) + +/** + * @brief Define all the states for the given node identifier. + * + * @param node_id Node identifier. + */ +#define Z_PINCTRL_STATES_DEFINE(node_id) \ + static const struct pinctrl_state \ + Z_PINCTRL_STATES_NAME(node_id)[] = { \ + UTIL_LISTIFY(DT_NUM_PINCTRL_STATES(node_id), \ + Z_PINCTRL_STATE_INIT, node_id) \ + }; + +#ifdef CONFIG_PINCTRL_STORE_REG +/** + * @brief Helper macro to initialize pin control config. + * + * @param node_id Node identifier. + */ +#define Z_PINCTRL_DEV_CONFIG_INIT(node_id) \ + { \ + .reg = DT_REG_ADDR(node_id), \ + .states = Z_PINCTRL_STATES_NAME(node_id), \ + .state_cnt = ARRAY_SIZE(Z_PINCTRL_STATES_NAME(node_id)), \ + } +#else +#define Z_PINCTRL_DEV_CONFIG_INIT(node_id) \ + { \ + .states = Z_PINCTRL_STATES_NAME(node_id), \ + .state_cnt = ARRAY_SIZE(Z_PINCTRL_STATES_NAME(node_id)), \ + } +#endif + +#ifdef CONFIG_PINCTRL_NON_STATIC +#define Z_PINCTRL_DEV_CONFIG_STATIC +#else +#define Z_PINCTRL_DEV_CONFIG_STATIC static +#endif + +#ifdef CONFIG_PINCTRL_DYNAMIC +#define Z_PINCTRL_DEV_CONFIG_CONST +#else +#define Z_PINCTRL_DEV_CONFIG_CONST const +#endif + +/** @endcond */ + +#if defined(CONFIG_PINCTRL_NON_STATIC) || defined(__DOXYGEN__) +/** + * @brief Declare pin control configuration for a given node identifier. + * + * This macro should be used by tests or applications using runtime pin control + * to declare the pin control configuration for a device. + * #PINCTRL_DT_DEV_CONFIG_GET can later be used to obtain a reference to such + * configuration. + * + * Only available if @kconfig{CONFIG_PINCTRL_NON_STATIC} is selected. + * + * @param node_id Node identifier. + */ +#define PINCTRL_DT_DEV_CONFIG_DECLARE(node_id) \ + extern Z_PINCTRL_DEV_CONFIG_CONST struct pinctrl_dev_config \ + Z_PINCTRL_DEV_CONFIG_NAME(node_id) +#endif /* defined(CONFIG_PINCTRL_NON_STATIC) || defined(__DOXYGEN__) */ + +/** + * @brief Define all pin control information for the given node identifier. + * + * This helper macro should be called together with device definition. It + * defines and initializes the pin control configuration for the device + * represented by node_id. Each pin control state (pinctrl-0, ..., pinctrl-N) is + * also defined and initialized. Note that states marked to be skipped will not + * be defined (refer to Z_PINCTRL_SKIP_STATE for more details). + * + * @param node_id Node identifier. + */ +#define PINCTRL_DT_DEFINE(node_id) \ + UTIL_LISTIFY(DT_NUM_PINCTRL_STATES(node_id), \ + Z_PINCTRL_STATE_PINS_DEFINE, node_id) \ + Z_PINCTRL_STATES_DEFINE(node_id) \ + Z_PINCTRL_DEV_CONFIG_CONST Z_PINCTRL_DEV_CONFIG_STATIC \ + struct pinctrl_dev_config Z_PINCTRL_DEV_CONFIG_NAME(node_id) = \ + Z_PINCTRL_DEV_CONFIG_INIT(node_id); + +/** + * @brief Define all pin control information for the given compatible index. + * + * @param inst Instance number. + * + * @see #PINCTRL_DT_DEFINE + */ +#define PINCTRL_DT_INST_DEFINE(inst) PINCTRL_DT_DEFINE(DT_DRV_INST(inst)) + +/** + * @brief Obtain a reference to the pin control configuration given a node + * identifier. + * + * @param node_id Node identifier. + */ +#define PINCTRL_DT_DEV_CONFIG_GET(node_id) &Z_PINCTRL_DEV_CONFIG_NAME(node_id) + +/** + * @brief Obtain a reference to the pin control configuration given current + * compatible instance number. + * + * @param inst Instance number. + * + * @see #PINCTRL_DT_DEV_CONFIG_GET + */ +#define PINCTRL_DT_INST_DEV_CONFIG_GET(inst) \ + PINCTRL_DT_DEV_CONFIG_GET(DT_DRV_INST(inst)) + +/** + * @brief Find the state configuration for the given state id. + * + * @param config Pin controller configuration. + * @param id Pin controller state id (see @ref PINCTRL_STATES). + * @param state Found state. + * + * @retval 0 If state has been found. + * @retval -ENOENT If the state has not been found. + */ +int pinctrl_lookup_state(const struct pinctrl_dev_config *config, uint8_t id, + const struct pinctrl_state **state); + +/** + * @brief Configure a set of pins. + * + * This function will configure the necessary hardware blocks to make the + * configuration immediately effective. + * + * @warning This function must never be used to configure pins used by an + * instantiated device driver. + * + * @param pins List of pins to be configured. + * @param pin_cnt Number of pins. + * @param reg Device register (optional, use #PINCTRL_REG_NONE if not used). + * + * @retval 0 If succeeded + * @retval -errno Negative errno for other failures. + */ +int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, + uintptr_t reg); + +/** + * @brief Apply a state directly from the provided state configuration. + * + * @param config Pin control configuration. + * @param state State. + * + * @retval 0 If succeeded + * @retval -errno Negative errno for other failures. + */ +static inline int pinctrl_apply_state_direct( + const struct pinctrl_dev_config *config, + const struct pinctrl_state *state) +{ + uintptr_t reg; + +#ifdef CONFIG_PINCTRL_STORE_REG + reg = config->reg; +#else + reg = PINCTRL_REG_NONE; +#endif + + return pinctrl_configure_pins(state->pins, state->pin_cnt, reg); +} + +/** + * @brief Apply a state from the given device configuration. + * + * @param config Pin control configuration. + * @param id Id of the state to be applied (see @ref PINCTRL_STATES). + * + * @retval 0 If succeeded. + * @retval -ENOENT If given state id does not exist. + * @retval -errno Negative errno for other failures. + */ +static inline int pinctrl_apply_state(const struct pinctrl_dev_config *config, + uint8_t id) +{ + int ret; + const struct pinctrl_state *state; + + ret = pinctrl_lookup_state(config, id, &state); + if (ret < 0) { + return ret; + } + + return pinctrl_apply_state_direct(config, state); +} + +#if defined(CONFIG_PINCTRL_DYNAMIC) || defined(__DOXYGEN__) +/** + * @defgroup pinctrl_interface_dynamic Dynamic Pin Control + * @{ + */ + +/** + * @brief Helper macro to define the pins of a pin control state from + * Devicetree. + * + * The name of the defined state pins variable is the same used by @p prop. This + * macro is expected to be used in conjunction with #PINCTRL_DT_STATE_INIT. + * + * @param node_id Node identifier containing @p prop. + * @param prop Property within @p node_id containing state configuration. + * + * @see #PINCTRL_DT_STATE_INIT + */ +#define PINCTRL_DT_STATE_PINS_DEFINE(node_id, prop) \ + static const pinctrl_soc_pin_t prop ## _pins[] = \ + Z_PINCTRL_STATE_PINS_INIT(node_id, prop); \ + +/** + * @brief Utility macro to initialize a pin control state. + * + * This macro should be used in conjunction with #PINCTRL_DT_STATE_PINS_DEFINE + * when using dynamic pin control to define an alternative state configuration + * stored in Devicetree. + * + * Example: + * + * @code{.devicetree} + * // board.dts + * + * /{ + * zephyr,user { + * // uart0_alt_default node contains alternative pin config + * uart0_alt_default = <&uart0_alt_default>; + * }; + * }; + * @endcode + * + * @code{.c} + * // application + * + * PINCTRL_DT_STATE_PINS_DEFINE(DT_PATH(zephyr_user), uart0_alt_default); + * + * static const struct pinctrl_state uart0_alt[] = { + * PINCTRL_DT_STATE_INIT(uart0_alt_default, PINCTRL_STATE_DEFAULT) + * }; + * @endcode + * + * @param prop Property name in Devicetree containing state configuration. + * @param state State represented by @p prop (see @ref PINCTRL_STATES). + * + * @see #PINCTRL_DT_STATE_PINS_DEFINE + */ +#define PINCTRL_DT_STATE_INIT(prop, state) \ + { \ + .id = state, \ + .pins = prop ## _pins, \ + .pin_cnt = ARRAY_SIZE(prop ## _pins) \ + } + +/** + * @brief Update states with a new set. + * + * @note In order to guarantee device drivers correct operation the same states + * have to be provided. For example, if @c default and @c sleep are in the + * current list of states, it is expected that the new array of states also + * contains both. + * + * @param config Pin control configuration. + * @param states New states to be set. + * @param state_cnt Number of new states to be set. + * + * @retval -EINVAL If the new configuration does not contain the same states as + * the current active configuration. + * @retval -ENOSYS If the functionality is not available. + * @retval 0 On success. + */ +int pinctrl_update_states(struct pinctrl_dev_config *config, + const struct pinctrl_state *states, + uint8_t state_cnt); + +/** @} */ +#else +static inline int pinctrl_update_states( + struct pinctrl_dev_config *config, + const struct pinctrl_state *states, uint8_t state_cnt) +{ + return -ENOSYS; +} +#endif /* defined(CONFIG_PINCTRL_DYNAMIC) || defined(__DOXYGEN__) */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_PINCTRL_H_ */ diff --git a/tests/drivers/pinctrl/api/CMakeLists.txt b/tests/drivers/pinctrl/api/CMakeLists.txt new file mode 100644 index 0000000000000..1825c3dabc4c0 --- /dev/null +++ b/tests/drivers/pinctrl/api/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(pinctrl_api) + +zephyr_include_directories(src) +zephyr_include_directories(../common) + +target_sources(app PRIVATE src/main.c src/pinctrl_test.c ../common/test_device.c) diff --git a/tests/drivers/pinctrl/api/Kconfig b/tests/drivers/pinctrl/api/Kconfig new file mode 100644 index 0000000000000..8102053cdb6f5 --- /dev/null +++ b/tests/drivers/pinctrl/api/Kconfig @@ -0,0 +1,13 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "pinctrl API Test" + +source "Kconfig.zephyr" + +config PINCTRL_TEST_STORE_REG + bool "Store register address" + select PINCTRL_STORE_REG + help + This option should be selected by unit tests that want to store the + register address of devices. diff --git a/tests/drivers/pinctrl/api/app.overlay b/tests/drivers/pinctrl/api/app.overlay new file mode 100644 index 0000000000000..d406814b84086 --- /dev/null +++ b/tests/drivers/pinctrl/api/app.overlay @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + + / { + pinctrl { + compatible = "vnd,pinctrl-test"; + + /* default state for device 0 */ + test_device0_default: test_device0_default { + pins1 { + pins = <0>; + bias-pull-up; + }; + pins2 { + pins = <1>; + bias-pull-down; + }; + }; + + /* sleep state for device 0 */ + test_device0_sleep: test_device0_sleep { + pins1 { + pins = <0>, <1>; + }; + }; + + /* alternative default state for device 0 */ + test_device0_alt_default: test_device0_alt_default { + pins1 { + pins = <2>; + bias-pull-down; + }; + pins2 { + pins = <3>; + bias-pull-up; + }; + }; + + /* alternative sleep state for device 0 */ + test_device0_alt_sleep: test_device0_alt_sleep { + pins1 { + pins = <2>, <3>; + }; + }; + + /* default state for device 1 */ + test_device1_default: test_device1_default { + pins1 { + pins = <10>, <11>, <12>; + }; + }; + + /* custom state "mystate" for device 1 */ + test_device1_mystate: test_device1_mystate { + pins1 { + pins = <10>; + }; + pins2 { + pins = <11>; + bias-pull-up; + }; + pins3 { + pins = <12>; + bias-pull-down; + }; + }; + }; + + zephyr,user { + test_device0_alt_default = <&test_device0_alt_default>; + test_device0_alt_sleep = <&test_device0_alt_sleep>; + }; + + test_device0: test_device@0 { + compatible = "vnd,pinctrl-device"; + reg = <0x0 0x1>; + pinctrl-0 = <&test_device0_default>; + pinctrl-1 = <&test_device0_sleep>; + pinctrl-names = "default", "sleep"; + }; + + test_device1: test_device@1 { + compatible = "vnd,pinctrl-device"; + reg = <0x1 0x1>; + pinctrl-0 = <&test_device1_default>; + pinctrl-1 = <&test_device1_mystate>; + pinctrl-names = "default", "mystate"; + }; +}; diff --git a/tests/drivers/pinctrl/api/prj.conf b/tests/drivers/pinctrl/api/prj.conf new file mode 100644 index 0000000000000..0ffb301538730 --- /dev/null +++ b/tests/drivers/pinctrl/api/prj.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_ZTEST_MOCKING=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_DYNAMIC=y diff --git a/tests/drivers/pinctrl/api/reg.conf b/tests/drivers/pinctrl/api/reg.conf new file mode 100644 index 0000000000000..955bd02ea4082 --- /dev/null +++ b/tests/drivers/pinctrl/api/reg.conf @@ -0,0 +1,4 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_PINCTRL_TEST_STORE_REG=y diff --git a/tests/drivers/pinctrl/api/src/main.c b/tests/drivers/pinctrl/api/src/main.c new file mode 100644 index 0000000000000..3f331b9f1a698 --- /dev/null +++ b/tests/drivers/pinctrl/api/src/main.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "test_device.h" + +/* test device 0 */ +#define TEST_DEVICE0 DT_NODELABEL(test_device0) +PINCTRL_DT_DEV_CONFIG_DECLARE(TEST_DEVICE0); +static struct pinctrl_dev_config *pcfg0 = PINCTRL_DT_DEV_CONFIG_GET(TEST_DEVICE0); + +/* test device 1 */ +#define TEST_DEVICE1 DT_NODELABEL(test_device1) +PINCTRL_DT_DEV_CONFIG_DECLARE(TEST_DEVICE1); +static struct pinctrl_dev_config *pcfg1 = PINCTRL_DT_DEV_CONFIG_GET(TEST_DEVICE1); + +/** + * @brief Test if configuration for device 0 has been stored as expected. + * + * Device 0 is also used to verify that certain states are skipped + * automatically, e.g. "sleep" if CONFIG_PM_DEVICE is not active. + * + * This test together with test_config_dev1 is used to verify that the whole + * set of macros used to define and initialize pin control config from + * Devicetree works as expected. + */ +static void test_config_dev0(void) +{ + const struct pinctrl_state *scfg; + + zassert_equal(pcfg0->state_cnt, 1, NULL); +#ifdef CONFIG_PINCTRL_STORE_REG + zassert_equal(pcfg0->reg, 0, NULL); +#endif + + scfg = &pcfg0->states[0]; + zassert_equal(scfg->id, PINCTRL_STATE_DEFAULT, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[0]), 0, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[0]), TEST_PULL_UP, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[1]), 1, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[1]), TEST_PULL_DOWN, NULL); +} + +/** + * @brief Test if configuration for device 1 has been stored as expected. + * + * Device 1 is also used to verify that custom states can be defined. + * + * @see test_config_dev0() + */ +static void test_config_dev1(void) +{ + const struct pinctrl_state *scfg; + + zassert_equal(pcfg1->state_cnt, 2, NULL); +#ifdef CONFIG_PINCTRL_STORE_REG + zassert_equal(pcfg1->reg, 1, NULL); +#endif + + scfg = &pcfg1->states[0]; + zassert_equal(scfg->id, PINCTRL_STATE_DEFAULT, NULL); + zassert_equal(scfg->pin_cnt, 3, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[0]), 10, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[0]), TEST_PULL_DISABLE, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[1]), 11, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[1]), TEST_PULL_DISABLE, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[2]), 12, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[2]), TEST_PULL_DISABLE, NULL); + + scfg = &pcfg1->states[1]; + zassert_equal(scfg->id, PINCTRL_STATE_MYSTATE, NULL); + zassert_equal(scfg->pin_cnt, 3, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[0]), 10, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[0]), TEST_PULL_DISABLE, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[1]), 11, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[1]), TEST_PULL_UP, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[2]), 12, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[2]), TEST_PULL_DOWN, NULL); +} + +/** + * @brief Test that pinctrl_lookup_state() works as expected + */ +static void test_lookup_state(void) +{ + int ret; + const struct pinctrl_state *scfg; + + ret = pinctrl_lookup_state(pcfg0, PINCTRL_STATE_DEFAULT, &scfg); + zassert_equal(ret, 0, NULL); + zassert_equal_ptr(scfg, &pcfg0->states[0], NULL); + + ret = pinctrl_lookup_state(pcfg0, PINCTRL_STATE_SLEEP, &scfg); + zassert_equal(ret, -ENOENT, NULL); +} + +/** + * @brief Test that pinctrl_apply_state() works as expected. + */ +static void test_apply_state(void) +{ + int ret; + + ztest_expect_data(pinctrl_configure_pins, pins, pcfg0->states[0].pins); + ztest_expect_value(pinctrl_configure_pins, pin_cnt, + pcfg0->states[0].pin_cnt); +#ifdef CONFIG_PINCTRL_STORE_REG + ztest_expect_value(pinctrl_configure_pins, reg, 0); +#else + ztest_expect_value(pinctrl_configure_pins, reg, PINCTRL_REG_NONE); +#endif + + ret = pinctrl_apply_state(pcfg0, PINCTRL_STATE_DEFAULT); + zassert_equal(ret, 0, NULL); +} + +/** Test device 0 alternative pins for default state */ +PINCTRL_DT_STATE_PINS_DEFINE(DT_PATH(zephyr_user), test_device0_alt_default); +/** Test device 0 alternative pins for sleep state */ +PINCTRL_DT_STATE_PINS_DEFINE(DT_PATH(zephyr_user), test_device0_alt_sleep); + +/** Test device 0 alternative states. */ +static const struct pinctrl_state test_device0_alt[] = { + PINCTRL_DT_STATE_INIT(test_device0_alt_default, PINCTRL_STATE_DEFAULT), +}; + +/** Test device 0 invalid alternative states. */ +static const struct pinctrl_state test_device0_alt_invalid[] = { + PINCTRL_DT_STATE_INIT(test_device0_alt_default, PINCTRL_STATE_DEFAULT), + /* sleep state is skipped (no CONFIG_PM_DEVICE), so it is invalid */ + PINCTRL_DT_STATE_INIT(test_device0_alt_sleep, PINCTRL_STATE_SLEEP), +}; + +/** + * @brief This test checks if pinctrl_update_states() works as expected. + */ +static void test_update_states(void) +{ + int ret; + const struct pinctrl_state *scfg; + + ret = pinctrl_update_states(pcfg0, test_device0_alt, + ARRAY_SIZE(test_device0_alt)); + zassert_equal(ret, 0, NULL); + + scfg = &pcfg0->states[0]; + zassert_equal(TEST_GET_PIN(scfg->pins[0]), 2, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[0]), TEST_PULL_DOWN, NULL); + zassert_equal(TEST_GET_PIN(scfg->pins[1]), 3, NULL); + zassert_equal(TEST_GET_PULL(scfg->pins[1]), TEST_PULL_UP, NULL); + + ret = pinctrl_update_states(pcfg0, test_device0_alt_invalid, + ARRAY_SIZE(test_device0_alt_invalid)); + zassert_equal(ret, -EINVAL, NULL); +} + +void test_main(void) +{ + ztest_test_suite(pinctrl_api, + ztest_unit_test(test_config_dev0), + ztest_unit_test(test_config_dev1), + ztest_unit_test(test_lookup_state), + ztest_unit_test(test_apply_state), + ztest_unit_test(test_update_states)); + ztest_run_test_suite(pinctrl_api); +} diff --git a/tests/drivers/pinctrl/api/src/pinctrl_soc.h b/tests/drivers/pinctrl/api/src/pinctrl_soc.h new file mode 100644 index 0000000000000..85195c2f3187a --- /dev/null +++ b/tests/drivers/pinctrl/api/src/pinctrl_soc.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * Testing specific helpers for pinctrl driver. + */ + +#ifndef ZEPHYR_TESTS_DRIVERS_PINCTRL_API_SRC_PINCTRL_SOC_H_ +#define ZEPHYR_TESTS_DRIVERS_PINCTRL_API_SRC_PINCTRL_SOC_H_ + +#include +#include + +/** + * @name Test pin configuration bit field positions and masks. + * + * Bits 31, 30: Pull config. + * Bits 29..0: Pin. + * + * @{ + */ + +/** Position of the pull field. */ +#define TEST_PULL_POS 30U +/** Mask of the pull field. */ +#define TEST_PULL_MSK 0x3U +/** Position of the pin field. */ +#define TEST_PIN_POS 0U +/** Mask for the pin field. */ +#define TEST_PIN_MSK 0x3FFFFFFFU + +/** @} */ + +/** + * @name Test pinctrl pull-up/down. + * @{ + */ + +/** Pull-up disabled. */ +#define TEST_PULL_DISABLE 0U +/** Pull-down enabled. */ +#define TEST_PULL_DOWN 1U +/** Pull-up enabled. */ +#define TEST_PULL_UP 2U + +/** @} */ + +/** + * @brief Utility macro to obtain pin pull configuration. + * + * @param pincfg Pin configuration bit field. + */ +#define TEST_GET_PULL(pincfg) (((pincfg) >> TEST_PULL_POS) & TEST_PULL_MSK) + +/** + * @brief Utility macro to obtain port and pin combination. + * + * @param pincfg Pin configuration bit field. + */ +#define TEST_GET_PIN(pincfg) (((pincfg) >> TEST_PIN_POS) & TEST_PIN_MSK) + +/** @cond INTERNAL_HIDDEN */ + +/** Test pin type */ +typedef uint32_t pinctrl_soc_pin_t; + +/** + * @brief Utility macro to initialize each pin. + * + * @param node_id Node identifier. + * @param prop Property name. + * @param idx Property entry index. + */ +#define Z_PINCTRL_STATE_PIN_INIT(node_id, prop, idx) \ + (((DT_PROP_BY_IDX(node_id, prop, idx) << TEST_PIN_POS) \ + & TEST_PIN_MSK) | \ + ((TEST_PULL_UP * DT_PROP(node_id, bias_pull_up)) \ + << TEST_PULL_POS) | \ + ((TEST_PULL_DOWN * DT_PROP(node_id, bias_pull_down)) \ + << TEST_PULL_POS) \ + ), + +/** + * @brief Utility macro to initialize state pins contained in a given property. + * + * @param node_id Node identifier. + * @param prop Property name describing state pins. + */ +#define Z_PINCTRL_STATE_PINS_INIT(node_id, prop) \ + {DT_FOREACH_CHILD_VARGS(DT_PROP_BY_IDX(node_id, prop, 0), \ + DT_FOREACH_PROP_ELEM, pins, \ + Z_PINCTRL_STATE_PIN_INIT)} + +/** @endcond */ + +#endif /* ZEPHYR_TESTS_DRIVERS_PINCTRL_API_SRC_PINCTRL_SOC_H_ */ diff --git a/tests/drivers/pinctrl/api/src/pinctrl_test.c b/tests/drivers/pinctrl/api/src/pinctrl_test.c new file mode 100644 index 0000000000000..0dff842f27f94 --- /dev/null +++ b/tests/drivers/pinctrl/api/src/pinctrl_test.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/* pinctrl test driver implemented as a mock */ + +int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, + uintptr_t reg) +{ + ztest_check_expected_data(pins, pin_cnt * sizeof(*pins)); + ztest_check_expected_value(pin_cnt); + ztest_check_expected_value(reg); + + return 0; +} diff --git a/tests/drivers/pinctrl/api/testcase.yaml b/tests/drivers/pinctrl/api/testcase.yaml new file mode 100644 index 0000000000000..f56eb6a56fa20 --- /dev/null +++ b/tests/drivers/pinctrl/api/testcase.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +tests: + drivers.pinctrl.api: + tags: drivers pinctrl + platform_allow: native_posix native_posix_64 + derivers.pinctrl.api_reg: + tags: drivers pinctrl + platform_allow: native_posix native_posix_64 + extra_args: CONF_FILE="prj.conf;reg.conf" diff --git a/tests/drivers/pinctrl/common/test_device.c b/tests/drivers/pinctrl/common/test_device.c new file mode 100644 index 0000000000000..827e47577519b --- /dev/null +++ b/tests/drivers/pinctrl/common/test_device.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT vnd_pinctrl_device + +#include "test_device.h" + +#include +#include + +int test_device_init(const struct device *dev) +{ + ARG_UNUSED(dev); + + return 0; +} + +#define PINCTRL_DEVICE_INIT(inst) \ + PINCTRL_DT_INST_DEFINE(inst) \ + \ + DEVICE_DT_INST_DEFINE(inst, test_device_init, NULL, NULL, NULL, \ + POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL); + +DT_INST_FOREACH_STATUS_OKAY(PINCTRL_DEVICE_INIT) diff --git a/tests/drivers/pinctrl/common/test_device.h b/tests/drivers/pinctrl/common/test_device.h new file mode 100644 index 0000000000000..769465ce8c274 --- /dev/null +++ b/tests/drivers/pinctrl/common/test_device.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_TESTS_DRIVERS_PINCTRL_COMMON_TEST_DEVICE_H_ +#define ZEPHYR_TESTS_DRIVERS_PINCTRL_COMMON_TEST_DEVICE_H_ + +#include + +/** Custom pinctrl state "mystate". */ +#define PINCTRL_STATE_MYSTATE PINCTRL_STATE_PRIV_START + +#endif /* ZEPHYR_TESTS_DRIVERS_PINCTRL_COMMON_TEST_DEVICE_H_ */