Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pinctrl — Pin Control Subsystem

The pinctrl subsystem is the Linux kernel framework for managing SoC (System-on-Chip) pins. It handles pin muxing (selecting which function a pin serves — e.g., UART TX vs GPIO) and pin configuration (electrical properties like pull-up, drive strength, slew rate).

Header: include/linux/pinctrl/pinctrl.h, include/linux/pinctrl/pinmux.h
Key files: drivers/pinctrl/core.c, drivers/pinctrl/devicetree.c
DT bindings: Documentation/devicetree/bindings/pinctrl/


Why Pinctrl Exists

Modern SoCs have hundreds of pins that can serve multiple functions:

┌──────────────────────────────────────────────────────┐
│                    SoC Pin                            │
│                                                      │
│  ┌─────────┐     ┌─────────┐     ┌─────────┐        │
│  │  UART   │     │  GPIO   │     │  SPI    │        │
│  │  TX     │     │  Pin 23 │     │  MOSI   │        │
│  └────┬────┘     └────┬────┘     └────┬────┘        │
│       │               │               │              │
│       └───────────────┼───────────────┘              │
│                       │                              │
│                  ┌────▼────┐                         │
│                  │ Pin Mux │                         │
│                  │ Select  │                         │
│                  └────┬────┘                         │
│                       │                              │
│                  ┌────▼────┐                         │
│                  │Physical │                         │
│                  │   Pin   │                         │
│                  └─────────┘                         │
└──────────────────────────────────────────────────────┘

Without pinctrl, each driver would need SoC-specific pin programming code. Pinctrl provides:

  1. A unified API for drivers to request pin configurations.
  2. Device tree integration for describing pin setups declaratively.
  3. Runtime pin switching (e.g., switch a pin from GPIO to I2C on demand).

Core Concepts

Pin Controller

A pin controller is a hardware block (or driver) that manages a set of pins. Each SoC has at least one pin controller.

struct pinctrl_desc {
    const char *name;
    const struct pinctrl_pin_desc *pins;
    unsigned int npins;
    const struct pinctrl_ops *pctlops;      /* pin control operations */
    const struct pinmux_ops *pmxops;        /* pin mux operations */
    const struct pinconf_ops *confops;      /* pin configuration ops */
    /* ... */
};

Pin States

Devices define pin states that describe how their pins should be configured:

StatePurpose
defaultNormal operation
initDuring driver probe (before device starts)
sleepLow-power state
idleIntermediate power state
gpioPin used as GPIO
spiPin used for SPI function

Device-Side API

Drivers request pin states via the pinctrl API:

#include <linux/pinctrl/consumer.h>

struct pinctrl *pinctrl;
struct pinctrl_state *state_default;

/* Get pinctrl handle */
pinctrl = devm_pinctrl_get(dev);

/* Get a specific state */
state_default = pinctrl_lookup_state(pinctrl, "default");

/* Apply the state */
pinctrl_select_state(pinctrl, state_default);

Pin Muxing

Pin muxing selects which function a pin serves. Each pin can typically serve one of several functions (e.g., UART, SPI, GPIO, I2C).

Pinmux Operations (Driver API)

struct pinmux_ops {
    int (*request)(struct pinctrl_dev *pctldev, unsigned int pin);
    void (*free)(struct pinctrl_dev *pctldev, unsigned int pin);
    int (*get_functions_count)(struct pinctrl_dev *pctldev);
    const char *(*get_function_name)(struct pinctrl_dev *pctldev,
                                     unsigned int selector);
    int (*get_function_groups)(struct pinctrl_dev *pctldev,
                               unsigned int selector,
                               const char *const **groups,
                               unsigned int *num_groups);
    int (*set_mux)(struct pinctrl_dev *pctldev, unsigned int func_selector,
                   unsigned int group_selector);
    int (*gpio_request_enable)(struct pinctrl_dev *pctldev,
                               struct pinctrl_gpio_range *range,
                               unsigned int pin);
    void (*gpio_disable_free)(struct pinctrl_dev *pctldev,
                              struct pinctrl_gpio_range *range,
                              unsigned int pin);
    int (*gpio_set_direction)(struct pinctrl_dev *pctldev,
                              struct pinctrl_gpio_range *range,
                              unsigned int pin, bool input);
};

Device Tree Muxing

/* Pin controller node */
&pinctrl {
    uart0_pins: uart0-pins {
        pins = "PIN_A_TX", "PIN_A_RX";
        function = "uart0";
    };

    spi0_pins: spi0-pins {
        pins = "PIN_B_MOSI", "PIN_B_MISO", "PIN_B_CLK", "PIN_B_CS";
        function = "spi0";
    };

    i2c1_pins: i2c1-pins {
        pins = "PIN_C_SDA", "PIN_C_SCL";
        function = "i2c1";
    };
};

/* Device node using the pins */
&uart0 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
    status = "okay";
};

&spi0 {
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&spi0_pins>;
    pinctrl-1 = <&spi0_sleep_pins>;
};

Pin Groups

Pin controllers organize pins into groups for convenience:

/* Group: all UART0 pins */
static const unsigned uart0_pins[] = { 10, 11 };  /* TX, RX */
static const unsigned uart0_pins_flow[] = { 10, 11, 12, 13 };  /* TX, RX, RTS, CTS */

static const struct pingrp_desc my_pin_groups[] = {
    PINGROUP("uart0", uart0_pins, uart0_mux),
    PINGROUP("uart0_flow", uart0_pins_flow, uart0_mux),
};

Pin Configuration

Pin configuration sets electrical properties of pins.

Configuration Parameters

ParameterDescriptionTypical Values
bias-disableNo pull resistorDefault state
bias-pull-upInternal pull-up20kΩ, 50kΩ
bias-pull-downInternal pull-down20kΩ, 50kΩ
drive-strengthOutput current2mA, 4mA, 8mA, 16mA
input-schmitt-enableSchmitt trigger inputBoolean
slew-rateSignal transition speed0=slow, 1=fast
output-lowDrive pin lowGPIO output
output-highDrive pin highGPIO output

Device Tree Configuration

&pinctrl {
    uart0_pins: uart0-pins {
        pins = "PIN_A_TX", "PIN_A_RX";
        function = "uart0";
        drive-strength = <8>;           /* 8mA */
        bias-pull-up;                   /* internal pull-up */
        input-schmitt-enable;           /* schmitt trigger */
    };

    spi0_pins: spi0-pins {
        pins = "PIN_B_MOSI", "PIN_B_CLK";
        function = "spi0";
        drive-strength = <12>;          /* 12mA (fast SPI) */
        bias-disable;
    };

    gpio_pins: gpio-pins {
        pins = "PIN_D_0", "PIN_D_1";
        function = "gpio";
        drive-strength = <4>;
        bias-pull-down;
        input-schmitt-enable;
    };
};

Pinconf Operations

struct pinconf_ops {
    bool is_generic;
    int (*pin_config_get)(struct pinctrl_dev *pctldev, unsigned int pin,
                          unsigned long *config);
    int (*pin_config_set)(struct pinctrl_dev *pctldev, unsigned int pin,
                          unsigned long *configs, unsigned int num_configs);
    int (*pin_config_group_get)(struct pinctrl_dev *pctldev,
                                unsigned int selector,
                                unsigned long *config);
    int (*pin_config_group_set)(struct pinctrl_dev *pctldev,
                                unsigned int selector,
                                unsigned long *configs,
                                unsigned int num_configs);
};

GPIO Ranges

GPIO and pinctrl are separate subsystems that must coordinate. GPIO ranges map GPIO numbers to pin controller pins:

┌──────────────────────────────────────────────────────┐
│                                                      │
│  GPIO Subsystem          Pinctrl Subsystem           │
│  ┌─────────────┐        ┌─────────────┐             │
│  │ GPIO 0-31   │◄──────►│ Pins 0-31   │             │
│  │ (gpiochip0) │  range │ (pinctrl0)  │             │
│  └─────────────┘        └─────────────┘             │
│                                                      │
│  ┌─────────────┐        ┌─────────────┐             │
│  │ GPIO 32-63  │◄──────►│ Pins 32-63  │             │
│  │ (gpiochip1) │  range │ (pinctrl1)  │             │
│  └─────────────┘        └─────────────┘             │
└──────────────────────────────────────────────────────┘

Registering GPIO Ranges

#include <linux/pinctrl/pinctrl.h>

static struct pinctrl_gpio_range my_gpio_range = {
    .name   = "my-gpio-chip",
    .id     = 0,
    .base   = 0,          /* GPIO base number */
    .pin_base = 0,        /* pin controller pin base */
    .npins  = 32,         /* number of pins */
    .gc     = &my_gpio_chip,
};

/* Register with pin controller */
pinctrl_add_gpio_range(pctldev, &my_gpio_range);

Device Tree GPIO Range

&pinctrl {
    gpio-ranges = <&pinctrl 0 0 32>,   /* GPIO 0-31 → pins 0-31 */
                  <&pinctrl 32 32 16>;  /* GPIO 32-47 → pins 32-47 */
};

GPIO Request Flow

gpio_request(gpio_num)
    │
    ▼
gpiochip request → pinctrl_gpio_request()
    │
    ▼
Pin controller checks:
    - Is this pin available? (not used by another function)
    - Switch pin to GPIO mode (if muxing needed)
    - Apply GPIO-specific configuration

Pin States and Power Management

Pin states are especially important for power management:

&pinctrl {
    /* Active state */
    uart0_active: uart0-active {
        pins = "PIN_A_TX", "PIN_A_RX";
        function = "uart0";
        drive-strength = <8>;
        bias-pull-up;
    };

    /* Sleep state (low power) */
    uart0_sleep: uart0-sleep {
        pins = "PIN_A_TX", "PIN_A_RX";
        function = "gpio";          /* switch to GPIO */
        drive-strength = <2>;       /* minimal drive */
        bias-pull-down;             /* pull-down to save power */
        output-low;                 /* drive low */
    };
};

&uart0 {
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart0_active>;
    pinctrl-1 = <&uart0_sleep>;
};

Runtime Pin State Switching

/* Driver switches pins for suspend */
static int my_device_suspend(struct device *dev)
{
    struct pinctrl *pinctrl = dev_get_pinctrl(dev);
    struct pinctrl_state *sleep_state;

    sleep_state = pinctrl_lookup_state(pinctrl, "sleep");
    pinctrl_select_state(pinctrl, sleep_state);

    return 0;
}

/* Driver switches pins for resume */
static int my_device_resume(struct device *dev)
{
    struct pinctrl *pinctrl = dev_get_pinctrl(dev);
    struct pinctrl_state *default_state;

    default_state = pinctrl_lookup_state(pinctrl, "default");
    pinctrl_select_state(pinctrl, default_state);

    return 0;
}

The PM core handles this automatically if pinctrl-names includes "sleep".


Pin Control in Device Tree: Full Example

/ {
    /* SoC-level pin controller */
    pinctrl: pinctrl@ff780000 {
        compatible = "vendor,soc-pinctrl";
        reg = <0xff780000 0x1000>;
        #pinctrl-cells = <1>;

        /* Pin definitions */
        pins {
            uart0_tx: uart0-tx {
                pins = "gpio10";
                function = "uart0";
                drive-strength = <8>;
                bias-pull-up;
            };

            uart0_rx: uart0-rx {
                pins = "gpio11";
                function = "uart0";
                bias-disable;
                input-schmitt-enable;
            };

            i2c0_sda: i2c0-sda {
                pins = "gpio20";
                function = "i2c0";
                drive-strength = <4>;
                bias-pull-up;
            };

            i2c0_scl: i2c0-scl {
                pins = "gpio21";
                function = "i2c0";
                drive-strength = <4>;
                bias-pull-up;
            };

            spi0_pins: spi0-pins {
                pins = "gpio30", "gpio31", "gpio32", "gpio33";
                function = "spi0";
                drive-strength = <12>;
                bias-disable;
            };

            /* Pin group (convenience) */
            uart0_pins: uart0-pins {
                pins = "gpio10", "gpio11";
                function = "uart0";
                drive-strength = <8>;
            };
        };

        /* GPIO ranges */
        gpio-ranges = <&pinctrl 0 0 64>;
    };
};

/* Devices reference pin states */
&uart0 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
    status = "okay";
};

&i2c0 {
    pinctrl-names = "default", "high-speed";
    pinctrl-0 = <&i2c0_sda &i2c0_scl>;
    pinctrl-1 = <&i2c0_hs_sda &i2c0_hs_scl>;
    status = "okay";
};

Common Pin Controller Drivers

DriverSoC VendorFile
pinctrl-bcm2835Broadcom (Raspberry Pi)drivers/pinctrl/bcm/pinctrl-bcm2835.c
pinctrl-sunxiAllwinnerdrivers/pinctrl/sunxi/pinctrl-sunxi.c
pinctrl-tegraNVIDIA Tegradrivers/pinctrl/tegra/pinctrl-tegra.c
pinctrl-qcomQualcommdrivers/pinctrl/qcom/pinctrl-*.c
pinctrl-mesonAmlogicdrivers/pinctrl/meson/pinctrl-meson.c
pinctrl-rockchipRockchipdrivers/pinctrl/pinctrl-rockchip.c
pinctrl-imxNXP i.MXdrivers/pinctrl/freescale/pinctrl-imx.c
pinctrl-stm32STMicroelectronicsdrivers/pinctrl/stm32/pinctrl-stm32.c

Debugging

View Current Pin States

# Debugfs interface (if available)
cat /sys/kernel/debug/pinctrl/pinctrl-maps
cat /sys/kernel/debug/pinctrl/pinctrl-handles

# Pin status
cat /sys/kernel/debug/pinctrl/pinctrl@ff780000/pins
cat /sys/kernel/debug/pinctrl/pinctrl@ff780000/pinmux-pins
cat /sys/kernel/debug/pinctrl/pinctrl@ff780000/pinconf-pins

Debugfs Output Example

Registered pinctrl groups:
  group: uart0-pins
    pin 10 (gpio10): function uart0
    pin 11 (gpio11): function uart0

Requested pin 10:
  device: uart0
  function: uart0
  hog: no

Pin config pins:
  pin 10 (gpio10): bias-pull-up drive-strength=8
  pin 11 (gpio11): bias-disable input-schmitt-enable

Common Issues

# "could not get pinctrl state"
# → Device tree pinctrl-0 reference is wrong
# → Pin controller driver not loaded

# "pin already requested"
# → Another driver already claimed this pin
# → Check: cat /sys/kernel/debug/pinctrl/*/pinmux-pins

# "invalid pin group"
# → Pin name in DT doesn't match driver's pin table
# → Check: cat /sys/kernel/debug/pinctrl/*/pins

Consumer API Summary

/* Get pinctrl handle for a device */
struct pinctrl *devm_pinctrl_get(struct device *dev);

/* Look up a named state */
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p,
                                           const char *name);

/* Apply a pin state */
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state);

/* Release (managed versions auto-release) */
void pinctrl_put(struct pinctrl *p);

/* GPIO-specific */
int pinctrl_gpio_request(unsigned gpio);
void pinctrl_gpio_free(unsigned gpio);
int pinctrl_gpio_direction_input(unsigned gpio);
int pinctrl_gpio_direction_output(unsigned gpio, int value);

Relation to Other Subsystems

  • pinctrl configures pins; GPIO uses pins as general-purpose I/O.
  • pinctrl selects functions; individual bus drivers (SPI, I2C, UART) use the selected function.
  • Device tree describes pin configurations; pinctrl applies them.
  • Clock framework often works alongside pinctrl (e.g., enabling a clock for a pin function).

Further Reading