README.md 14 KB

Pin Control Framework

Introduction

The Pin Control (pinctrl) framework in RT-Thread provides a standardized way to configure pin multiplexing, electrical properties, and GPIO functionality. It allows device drivers to dynamically configure pins without hard-coding hardware-specific details.

General Overview

Pin control is essential for:

  • Pin Multiplexing: Selecting between different functions for the same physical pin (UART, SPI, I2C, GPIO, etc.)
  • Electrical Configuration: Setting pull-up/pull-down resistors, drive strength, and other electrical properties
  • Dynamic Reconfiguration: Changing pin configurations at runtime based on system state
  • Power Management: Configuring pins for low-power modes
  • GPIO Management: Coordinating GPIO and peripheral pin usage

RT-Thread Implementation

The RT-Thread pinctrl framework, located in components/drivers/pinctrl/, integrates with the pin device driver framework and provides:

  1. Consumer API: Functions for device drivers to apply pin configurations
  2. Provider API: Framework for implementing pinctrl drivers
  3. Device Tree Integration: Automatic pin configuration from DT
  4. Pin States: Multiple named configurations per device (default, sleep, etc.)
  5. GPIO Request: Coordination between GPIO and peripheral usage

Kconfig Configuration

Main Configuration

menuconfig RT_USING_PINCTRL
    bool "Using Pin controllers device drivers"
    depends on RT_USING_DM
    depends on RT_USING_PIN
    default n

Location in menuconfig:

RT-Thread Components → Device Drivers → Using Pin controllers device drivers

Dependencies:

  • RT_USING_DM: Device driver model required
  • RT_USING_PIN: Pin device driver framework required

Default: Disabled (opt-in feature)

Pinctrl Driver Options

SCMI Pinctrl Driver

config RT_PINCTRL_SCMI
    bool "Pinctrl driver via ARM SCMI interface"
    depends on RT_USING_PINCTRL
    depends on RT_FIRMWARE_ARM_SCMI
    default n

Supports pin control through ARM SCMI interface.

Single-Register Pinctrl Driver

config RT_PINCTRL_SINGLE
    bool "Single Pinctrl driver"
    depends on RT_USING_PINCTRL
    default n

Supports simple register-based pin controllers.

Device Tree Bindings

Pinctrl Provider Properties

Pin controllers export their configuration capability using:

#pinctrl-cells = <n>;            /* Number of cells in pinctrl specifier */

Pinctrl Consumer Properties

Devices reference pin configurations using:

pinctrl-names = "default", "sleep";  /* State names */
pinctrl-0 = <&state0_pins>;          /* Pins for state 0 (default) */
pinctrl-1 = <&state1_pins>;          /* Pins for state 1 (sleep) */

Example: Pin Controller

pio: pinctrl@1c20800 {
    compatible = "vendor,pinctrl";
    reg = <0x1c20800 0x400>;
    #pinctrl-cells = <1>;
    
    /* Pin group definitions */
    uart0_pins: uart0-pins {
        pins = "PB8", "PB9";
        function = "uart0";
        drive-strength = <40>;
        bias-pull-up;
    };
    
    uart0_sleep_pins: uart0-sleep-pins {
        pins = "PB8", "PB9";
        function = "gpio";
        bias-disable;
    };
    
    spi0_pins: spi0-pins {
        pins = "PC0", "PC1", "PC2", "PC3";
        function = "spi0";
        drive-strength = <40>;
    };
    
    i2c0_pins: i2c0-pins {
        pins = "PA11", "PA12";
        function = "i2c0";
        bias-pull-up;
    };
};

Consumer Usage Examples

/* UART with pin configuration */
uart0: serial@1c28000 {
    compatible = "vendor,uart";
    reg = <0x1c28000 0x400>;
    
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart0_pins>;
    pinctrl-1 = <&uart0_sleep_pins>;
    
    status = "okay";
};

/* SPI with single pin state */
spi0: spi@1c68000 {
    compatible = "vendor,spi";
    reg = <0x1c68000 0x1000>;
    
    pinctrl-names = "default";
    pinctrl-0 = <&spi0_pins>;
    
    status = "okay";
};

/* I2C with pin configuration */
i2c0: i2c@1c2ac00 {
    compatible = "vendor,i2c";
    reg = <0x1c2ac00 0x400>;
    
    pinctrl-names = "default";
    pinctrl-0 = <&i2c0_pins>;
    
    status = "okay";
};

Application Layer API

Overview

The pinctrl API allows device drivers to apply pin configurations automatically from device tree or manually by name/index.

Applying Pin Configurations

rt_pin_ctrl_confs_apply

rt_err_t rt_pin_ctrl_confs_apply(struct rt_device *device, int index);

Apply pin configuration by index.

Parameters:

  • device: Device structure pointer
  • index: Configuration index (0 for pinctrl-0, 1 for pinctrl-1, etc.)

Returns:

  • RT_EOK on success
  • Error code on failure

Example:

struct rt_device *dev = &pdev->parent;

/* Apply default pin configuration (pinctrl-0) */
ret = rt_pin_ctrl_confs_apply(dev, 0);
if (ret != RT_EOK) {
    LOG_E("Failed to apply pin configuration: %d", ret);
    return ret;
}

rt_pin_ctrl_confs_apply_by_name

rt_err_t rt_pin_ctrl_confs_apply_by_name(struct rt_device *device, const char *name);

Apply pin configuration by state name.

Parameters:

  • device: Device structure pointer
  • name: State name (matches entry in pinctrl-names)

Returns:

  • RT_EOK on success
  • Error code on failure

Example:

/* Apply default pin configuration */
rt_pin_ctrl_confs_apply_by_name(dev, "default");

/* Later, switch to sleep configuration */
rt_pin_ctrl_confs_apply_by_name(dev, "sleep");

rt_pin_ctrl_confs_lookup

rt_ssize_t rt_pin_ctrl_confs_lookup(struct rt_device *device, const char *name);

Look up the index of a named pin configuration.

Parameters:

  • device: Device structure pointer
  • name: State name to look up

Returns:

  • Configuration index on success
  • Negative error code on failure

Example:

/* Find index of "sleep" configuration */
rt_ssize_t idx = rt_pin_ctrl_confs_lookup(dev, "sleep");
if (idx >= 0) {
    rt_pin_ctrl_confs_apply(dev, idx);
}

GPIO-Related Functions

rt_pin_get_named_pin

rt_ssize_t rt_pin_get_named_pin(struct rt_device *dev, const char *propname, 
                                int index, rt_uint8_t *out_mode, rt_uint8_t *out_value);

Get a GPIO pin from device tree property.

Parameters:

  • dev: Device structure
  • propname: Property name (e.g., "reset-gpios", "enable-gpios")
  • index: Pin index in the property
  • out_mode: Optional output for pin mode
  • out_value: Optional output for pin value

Returns:

  • Pin number on success
  • Negative error code on failure

Example:

rt_uint8_t mode, value;
rt_ssize_t reset_pin;

/* Get reset GPIO from device tree */
reset_pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, &mode, &value);
if (reset_pin >= 0) {
    rt_pin_mode(reset_pin, PIN_MODE_OUTPUT);
    rt_pin_write(reset_pin, PIN_LOW);  /* Assert reset */
}

rt_pin_get_named_pin_count

rt_ssize_t rt_pin_get_named_pin_count(struct rt_device *dev, const char *propname);

Get the number of GPIOs in a device tree property.

Parameters:

  • dev: Device structure
  • propname: Property name

Returns:

  • Number of pins
  • Negative error code on failure

Complete Application Example

Example: SPI Driver with Pinctrl

#include <rtthread.h>
#include <drivers/platform.h>
#include <drivers/spi.h>
#include <drivers/pin.h>

struct spi_device {
    void *base;
    int irq;
    struct rt_clk *clk;
    rt_ssize_t cs_pin;
    struct rt_spi_bus spi_bus;
};

static rt_err_t spi_probe(struct rt_platform_device *pdev)
{
    rt_err_t ret;
    struct rt_device *dev = &pdev->parent;
    struct spi_device *spi;
    
    /* Allocate device structure */
    spi = rt_calloc(1, sizeof(*spi));
    if (!spi)
        return -RT_ENOMEM;
    
    /* Map MMIO region */
    spi->base = rt_dm_dev_iomap(dev, 0);
    if (!spi->base) {
        ret = -RT_ERROR;
        goto err_free;
    }
    
    /* Apply pin configuration from device tree */
    ret = rt_pin_ctrl_confs_apply_by_name(dev, "default");
    if (ret != RT_EOK) {
        LOG_E("Failed to apply pin configuration: %d", ret);
        goto err_unmap;
    }
    
    /* Get optional chip select GPIO */
    spi->cs_pin = rt_pin_get_named_pin(dev, "cs-gpios", 0, RT_NULL, RT_NULL);
    if (spi->cs_pin >= 0) {
        rt_pin_mode(spi->cs_pin, PIN_MODE_OUTPUT);
        rt_pin_write(spi->cs_pin, PIN_HIGH);  /* Deassert CS */
        LOG_I("Using GPIO %d for CS", spi->cs_pin);
    }
    
    /* Get clock */
    spi->clk = rt_clk_get_by_name(dev, "spi");
    if (!spi->clk) {
        LOG_E("Failed to get SPI clock");
        ret = -RT_ERROR;
        goto err_unmap;
    }
    
    ret = rt_clk_prepare_enable(spi->clk);
    if (ret != RT_EOK) {
        goto err_put_clk;
    }
    
    /* Initialize SPI bus */
    spi->spi_bus.parent.user_data = spi;
    ret = rt_spi_bus_register(&spi->spi_bus, rt_dm_dev_get_name(dev), &spi_ops);
    if (ret != RT_EOK) {
        goto err_disable_clk;
    }
    
    pdev->priv = spi;
    LOG_I("SPI device registered");
    
    return RT_EOK;
    
err_disable_clk:
    rt_clk_disable_unprepare(spi->clk);
err_put_clk:
    rt_clk_put(spi->clk);
err_unmap:
    rt_iounmap(spi->base);
err_free:
    rt_free(spi);
    return ret;
}

static rt_err_t spi_suspend(struct rt_device *dev)
{
    /* Switch to sleep pin configuration */
    return rt_pin_ctrl_confs_apply_by_name(dev, "sleep");
}

static rt_err_t spi_resume(struct rt_device *dev)
{
    /* Restore default pin configuration */
    return rt_pin_ctrl_confs_apply_by_name(dev, "default");
}

static const struct rt_ofw_node_id spi_ofw_ids[] = {
    { .compatible = "vendor,spi" },
    { /* sentinel */ }
};

static struct rt_platform_driver spi_driver = {
    .name = "spi",
    .ids = spi_ofw_ids,
    .probe = spi_probe,
};

RT_PLATFORM_DRIVER_EXPORT(spi_driver);

Driver Implementation Guide

Key Structures

rt_pin_ops (with pinctrl)

struct rt_pin_ops {
    /* Standard pin operations */
    void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_uint8_t mode);
    void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_uint8_t value);
    rt_ssize_t (*pin_read)(struct rt_device *device, rt_base_t pin);
    
    /* Pinctrl-specific operations */
    rt_err_t (*pin_ctrl_confs_apply)(struct rt_device *device, void *fw_conf_np);
    rt_err_t (*pin_ctrl_gpio_request)(struct rt_device *device, rt_base_t gpio, rt_uint32_t flags);
};

Pin Configuration Parameters

Common pin configuration parameters (from PIN_CONFIG_* enum):

  • PIN_CONFIG_BIAS_DISABLE: Disable bias (no pull-up/down)
  • PIN_CONFIG_BIAS_PULL_UP: Enable pull-up resistor
  • PIN_CONFIG_BIAS_PULL_DOWN: Enable pull-down resistor
  • PIN_CONFIG_DRIVE_STRENGTH: Set output drive strength
  • PIN_CONFIG_INPUT_ENABLE: Enable input buffer
  • PIN_CONFIG_OUTPUT_ENABLE: Enable output buffer
  • PIN_CONFIG_INPUT_DEBOUNCE: Set input debounce time
  • PIN_CONFIG_SLEW_RATE: Control signal slew rate

Best Practices

For Consumer Drivers

  1. Apply pin configuration early: During probe, before accessing hardware
  2. Use named states: More maintainable than numeric indices
  3. Handle missing pinctrl gracefully: Not all platforms require it
  4. Support power management: Switch pin states for suspend/resume
  5. Check GPIO availability: Verify GPIO pins exist before using them

Common Patterns

Basic Pin Configuration

/* Apply default pin configuration */
ret = rt_pin_ctrl_confs_apply_by_name(dev, "default");
if (ret != RT_EOK && ret != -RT_ENOSYS) {
    /* Real error, not just missing pinctrl */
    LOG_E("Pin configuration failed: %d", ret);
    return ret;
}

Power Management with Pinctrl

static rt_err_t device_suspend(struct rt_device *dev)
{
    /* Save device state */
    
    /* Apply sleep pin configuration */
    rt_pin_ctrl_confs_apply_by_name(dev, "sleep");
    
    /* Disable clocks, etc. */
    return RT_EOK;
}

static rt_err_t device_resume(struct rt_device *dev)
{
    /* Enable clocks, etc. */
    
    /* Restore default pin configuration */
    rt_pin_ctrl_confs_apply_by_name(dev, "default");
    
    /* Restore device state */
    return RT_EOK;
}

GPIO with Pinctrl

/* Get multiple GPIOs */
rt_ssize_t num_gpios = rt_pin_get_named_pin_count(dev, "reset-gpios");
for (int i = 0; i < num_gpios; i++) {
    rt_ssize_t pin = rt_pin_get_named_pin(dev, "reset-gpios", i, 
                                          RT_NULL, RT_NULL);
    if (pin >= 0) {
        rt_pin_mode(pin, PIN_MODE_OUTPUT);
        rt_pin_write(pin, PIN_LOW);
    }
}

Troubleshooting

Common Issues

  1. Pin configuration not applied

    • Check device tree: Ensure pinctrl-* properties exist
    • Check compatible: Verify pinctrl driver is loaded
    • Check Kconfig: Enable RT_USING_PINCTRL
  2. GPIO conflicts

    • Check pin multiplexing: Pin may be claimed by peripheral
    • Check pinctrl configuration: May conflict with GPIO usage
    • Use pin_ctrl_gpio_request for coordination
  3. Pin not working after configuration

    • Check electrical properties: Drive strength, pull resistors
    • Check pin function: Verify correct function is selected
    • Check hardware: Verify pin is connected correctly

Performance Considerations

Memory Usage

  • Pin configuration cached in device tree nodes
  • Minimal runtime overhead
  • Pin states stored per device

Timing

  • Configuration application: Usually fast (register writes)
  • No complex calculations required
  • One-time setup during probe

Related Modules

  • pin: Core pin device driver framework
  • gpio: GPIO functionality
  • clk: May need clocks enabled for pin configuration
  • pmdomain: Power domain coordination

References