Tüm eğitimler
TEKNİK REHBER GÖMÜLÜ LİNUX PINCTRL / GPIO 2026

pinctrl — Linux Pin Kontrol Alt Sistemi
Pin Mux, Pin Config & GPIO Entegrasyonu

SoC pin multiplexing mimarisi, controller sürücü yazımı, DT binding ve GPIO entegrasyonu — üretim kalitesinde pinctrl rehberi.

00 pinctrl neden var — pin multiplexing ve GPIO çakışması

Modern SoC'larda her fiziksel pin birden fazla fonksiyon için kullanılabilir; pinctrl bu çakışmayı merkezi, çekişme-önleyici bir framework ile çözer.

Bir ARM SoC'da yüzlerce fiziksel pin bulunur, ancak bunların büyük çoğunluğu birden fazla donanım bloğuna bağlanabilir: aynı pin hem UART TX hem SPI MOSI hem de GPIO olarak kullanılabilir. Hangi fonksiyonun aktif olduğunu belirlemek için SoC içinde pin multiplexer (mux) register'ları vardır. Linux pinctrl alt sistemi (drivers/pinctrl/) bu register'ları merkezi biçimde yönetir; iki sürücünün aynı pini talep etmesini engeller.

  Fiziksel pin PA7
  ┌─────────────────────────────────────────────────────┐
  │  Olası fonksiyonlar:                                │
  │    func0: GPIO_A7                                   │
  │    func1: UART2_TX                                  │
  │    func2: SPI1_MOSI                                 │
  │    func3: PWM3                                      │
  └────────────────┬────────────────────────────────────┘
                   │
         MUX_REG[15:14] = 0b01 → UART2_TX seçili
                   │
  +----------------v--------------------------------+
  |          pinctrl core                           |
  |  - Çakışma tespiti: aynı pin iki kez talep?    |
  |  - state yönetimi: default / sleep / idle       |
  |  - consumer API: pinctrl_get/select_state()     |
  +----------------+--------------------------------+
                   |
     +-------------+------------+
     |             |            |
  UART sürücü  SPI sürücü  GPIO sürücü
  (fonksiyon   (reddedilir  (GPIO mod
   seçildi)     çakışma)     talep eder)
    

Pinctrl olmadan yaşanan sorunlar

SorunAçıklama
Pin çakışmasıİki sürücü aynı pini farklı fonksiyon için yapılandırır — öngörülemeyen davranış
Enerji israfıKullanılmayan pin'lerin floating bırakılması akım tüketimine neden olur
Güvenlik açığıDebug/JTAG pinleri üretimde devre dışı bırakılmazsa saldırı vektörü oluşur
Uyku sorunuUyku sırasında pin'ler yanlış durumda kalırsa uyku akımı artar
Platform kodu tekrarıHer SoC kendi mux kodu yazıyordu — kernel 3.3 öncesi kaos
pinFiziksel bağlantı noktası; pinctrl'de unique ID ile temsil edilir
pin groupBirlikte yapılandırılan pin kümesi (ör. UART0 = {TX, RX, CTS, RTS})
functionBir pin grubunun alabileceği donanım bloğu rolü (uart0, spi1, gpio)
stateBelirli bir ana durumda geçerli olan pin konfigürasyonu kümesi
pinctrl_mapDevice ↔ state ↔ group ↔ function eşleştirme tablosu
NOT

pinctrl başlık dosyaları: <linux/pinctrl/consumer.h> (consumer API — çoğu sürücünün doğrudan kullanmadığı, framework otomatik uygular), <linux/pinctrl/pinctrl.h> ve <linux/pinctrl/pinmux.h> ve <linux/pinctrl/pinconf.h> (provider API).

Bu bölümde

  • SoC pin mux: her fiziksel pin birden fazla fonksiyon arasında donanım register'ıyla seçilir
  • Çakışma tespiti, enerji yönetimi, güvenlik: pinctrl'in sağladığı merkezi kontrol
  • Pin, pin group, function, state: temel pinctrl kavramları
  • Linux 3.3: pinctrl framework birleşik hale geldi

01 pinctrl_desc ve pinctrl_ops — controller sürücü yazımı

Pinctrl controller sürücüsünün çekirdeğe sunduğu pinctrl_desc yapısı ve zorunlu ops callback'leri.

pinctrl_desc ve pin tanımları

my_pinctrl.c — pinctrl_desc
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>

/* SoC'un tüm pin'lerini tanımla */
static const struct pinctrl_pin_desc my_soc_pins[] = {
    PINCTRL_PIN(0,  "PA0"),
    PINCTRL_PIN(1,  "PA1"),
    PINCTRL_PIN(2,  "PA2"),
    /* ... */
    PINCTRL_PIN(7,  "PA7"),
    PINCTRL_PIN(8,  "PB0"),
    PINCTRL_PIN(9,  "PB1"),
    /* ... toplam N pin */
    PINCTRL_PIN(127, "PH7"),
};

/* pinctrl controller private data */
struct my_pinctrl_priv {
    void                __iomem     *base;
    struct pinctrl_dev             *pctldev;
    struct gpio_chip                gc;
    struct device                  *dev;
    spinlock_t                      lock;
};

/* pinctrl_ops: pin/group sorgulama callback'leri */
static int my_get_groups_count(struct pinctrl_dev *pctldev)
{
    return ARRAY_SIZE(my_soc_groups);
}

static const char *my_get_group_name(struct pinctrl_dev *pctldev,
                                      unsigned selector)
{
    return my_soc_groups[selector].name;
}

static int my_get_group_pins(struct pinctrl_dev *pctldev,
                               unsigned           selector,
                               const unsigned int **pins,
                               unsigned int       *num_pins)
{
    *pins     = my_soc_groups[selector].pins;
    *num_pins = my_soc_groups[selector].num_pins;
    return 0;
}

static void my_pin_dbg_show(struct pinctrl_dev *pctldev,
                             struct seq_file    *s,
                             unsigned            offset)
{
    struct my_pinctrl_priv *priv = pinctrl_dev_get_drvdata(pctldev);
    u32 mux_val = readl(priv->base + MUX_REG(offset));

    seq_printf(s, " func=%u pull=%s drv=%umA",
               FIELD_GET(MUX_FUNC_MASK, mux_val),
               (mux_val & PIN_PULL_UP) ? "up" :
               (mux_val & PIN_PULL_DN) ? "down" : "none",
               FIELD_GET(DRV_MA_MASK, mux_val) * 4 + 4);
}

static const struct pinctrl_ops my_pctrl_ops = {
    .get_groups_count  = my_get_groups_count,
    .get_group_name    = my_get_group_name,
    .get_group_pins    = my_get_group_pins,
    .pin_dbg_show      = my_pin_dbg_show,
    .dt_node_to_map    = pinconf_generic_dt_node_to_map_all,
    .dt_free_map       = pinctrl_utils_free_map,
};

static const struct pinctrl_desc my_pinctrl_desc = {
    .name    = "my-soc-pinctrl",
    .pins    = my_soc_pins,
    .npins   = ARRAY_SIZE(my_soc_pins),
    .pctlops = &my_pctrl_ops,
    .pmxops  = &my_pinmux_ops,   /* Bölüm 02 */
    .confops = &my_pinconf_ops,  /* Bölüm 03 */
    .owner   = THIS_MODULE,
};

Pin group tanımları

my_pinctrl.c — pin groups
/* UART0 pin grubu — TX, RX, CTS, RTS */
static const unsigned int uart0_pins[] = { 2, 3, 4, 5 };

/* SPI1 pin grubu — CLK, MOSI, MISO, CS0 */
static const unsigned int spi1_pins[] = { 10, 11, 12, 13 };

/* I2C0 pin grubu — SCL, SDA */
static const unsigned int i2c0_pins[] = { 20, 21 };

struct my_pin_group {
    const char         *name;
    const unsigned int *pins;
    unsigned int        num_pins;
};

static const struct my_pin_group my_soc_groups[] = {
    { "uart0_grp", uart0_pins, ARRAY_SIZE(uart0_pins) },
    { "spi1_grp",  spi1_pins,  ARRAY_SIZE(spi1_pins)  },
    { "i2c0_grp",  i2c0_pins,  ARRAY_SIZE(i2c0_pins)  },
    /* ... */
};

Probe ve pinctrl_register

my_pinctrl.c — probe
static int my_pinctrl_probe(struct platform_device *pdev)
{
    struct my_pinctrl_priv *priv;
    struct pinctrl_dev     *pctldev;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    priv->dev = &pdev->dev;
    spin_lock_init(&priv->lock);
    platform_set_drvdata(pdev, priv);

    /* Pinctrl'e kaydet — devm_ versiyonu otomatik unregister */
    pctldev = devm_pinctrl_register(&pdev->dev,
                                     &my_pinctrl_desc,
                                     priv);
    if (IS_ERR(pctldev))
        return dev_err_probe(&pdev->dev, PTR_ERR(pctldev),
                             "pinctrl kaydi basarisiz\n");

    priv->pctldev = pctldev;
    return 0;
}

Bu bölümde

  • PINCTRL_PIN(id, "isim") makrosuyla pin tanımları; tüm fiziksel pinler listelenmeli
  • pinctrl_ops: get_groups_count, get_group_name, get_group_pins — zorunlu trio
  • dt_node_to_map = pinconf_generic_dt_node_to_map_all: generic DT parsing
  • devm_pinctrl_register(): probe'da tercih edilmeli; driver_data olarak priv

02 pinmux_ops — pin fonksiyon seçimi

pinmux_ops, pin gruplarına hangi donanım fonksiyonunun atanacağını belirleyen callback kümesidir.

Function (fonksiyon) tanımları

my_pinctrl.c — functions
struct my_pmx_func {
    const char  *name;
    const char **groups;      /* Bu fonksiyonun kullandığı grup isimleri */
    unsigned int num_groups;
};

static const char *uart0_groups[]  = { "uart0_grp" };
static const char *spi1_groups[]   = { "spi1_grp" };
static const char *i2c0_groups[]   = { "i2c0_grp" };
static const char *gpio_a_groups[] = { "gpio_a_grp" };

static const struct my_pmx_func my_soc_funcs[] = {
    [0] = { "uart0",  uart0_groups,  ARRAY_SIZE(uart0_groups)  },
    [1] = { "spi1",   spi1_groups,   ARRAY_SIZE(spi1_groups)   },
    [2] = { "i2c0",   i2c0_groups,   ARRAY_SIZE(i2c0_groups)   },
    [3] = { "gpio_a", gpio_a_groups, ARRAY_SIZE(gpio_a_groups) },
};

pinmux_ops callback implementasyonu

my_pinctrl.c — pinmux_ops
static int my_pmx_get_functions_count(struct pinctrl_dev *pctldev)
{
    return ARRAY_SIZE(my_soc_funcs);
}

static const char *my_pmx_get_function_name(struct pinctrl_dev *pctldev,
                                              unsigned            selector)
{
    return my_soc_funcs[selector].name;
}

static int my_pmx_get_function_groups(struct pinctrl_dev  *pctldev,
                                       unsigned             selector,
                                       const char        * const **groups,
                                       unsigned            *num_groups)
{
    *groups     = my_soc_funcs[selector].groups;
    *num_groups = my_soc_funcs[selector].num_groups;
    return 0;
}

/*
 * set_mux: gerçek donanım yazımı — mux register'ına fonksiyon bitlerini yaz
 * group_selector: hangi pin grubu
 * func_selector:  hangi fonksiyon
 */
static int my_pmx_set_mux(struct pinctrl_dev *pctldev,
                           unsigned int        func_selector,
                           unsigned int        group_selector)
{
    struct my_pinctrl_priv    *priv = pinctrl_dev_get_drvdata(pctldev);
    const struct my_pin_group *grp  = &my_soc_groups[group_selector];
    unsigned long              flags;
    int                        i;

    spin_lock_irqsave(&priv->lock, flags);

    for (i = 0; i < grp->num_pins; i++) {
        unsigned int pin = grp->pins[i];
        u32 reg_val      = readl(priv->base + MUX_REG(pin));

        /* Fonksiyon bitlerini temizle ve yaz */
        reg_val = (reg_val & ~MUX_FUNC_MASK) |
                  FIELD_PREP(MUX_FUNC_MASK, func_selector);

        writel(reg_val, priv->base + MUX_REG(pin));
    }

    spin_unlock_irqrestore(&priv->lock, flags);
    return 0;
}

/* GPIO request: pin'i GPIO moduna al */
static int my_pmx_gpio_request_enable(struct pinctrl_dev *pctldev,
                                       struct pinctrl_gpio_range *range,
                                       unsigned int              offset)
{
    struct my_pinctrl_priv *priv = pinctrl_dev_get_drvdata(pctldev);
    u32 reg_val;
    unsigned long flags;

    spin_lock_irqsave(&priv->lock, flags);
    reg_val = readl(priv->base + MUX_REG(offset));
    reg_val = (reg_val & ~MUX_FUNC_MASK) |
              FIELD_PREP(MUX_FUNC_MASK, MUX_FUNC_GPIO);
    writel(reg_val, priv->base + MUX_REG(offset));
    spin_unlock_irqrestore(&priv->lock, flags);

    return 0;
}

static const struct pinmux_ops my_pinmux_ops = {
    .get_functions_count  = my_pmx_get_functions_count,
    .get_function_name    = my_pmx_get_function_name,
    .get_function_groups  = my_pmx_get_function_groups,
    .set_mux              = my_pmx_set_mux,
    .gpio_request_enable  = my_pmx_gpio_request_enable,
    .gpio_disable_free    = my_pmx_gpio_disable_free,
    .strict               = true,  /* çakışma = hata */
};
set_muxDonanım mux register'ına fonksiyon bitlerini yazar; spinlock ile korunmalı
gpio_request_enablegpiod_request() çağrıldığında pin'i GPIO fonksiyonuna alır
gpio_disable_freegpiod_free() sonrası pin'i başlangıç durumuna döndürür
strict = trueAynı pin iki kez talep edilirse -EBUSY döner; üretim için önerilir

Bu bölümde

  • Function: birden fazla grubu kapsayan donanım bloğu rolü (uart0, spi1, gpio)
  • set_mux: MUX_REG'e fonksiyon indeksi yazar; tüm grup pinlerine uygulanır
  • gpio_request_enable: gpiod_request() → pin GPIO moduna geçer
  • strict = true: çakışma tespiti; iki sürücü aynı pini talep edemez

03 pinconf_ops — pull-up/down, drive strength, slew rate

pinconf_ops, bireysel pinlerin elektriksel konfigürasyonunu — pull-up, pull-down, drive strength, slew rate, schmitt trigger — yönetir.

Generic pinconf parametreleri

Parametre enumAnlamıTipik değer
PIN_CONFIG_BIAS_PULL_UPPull-up direnci etkinleştirDirenç değeri (Ohm) veya 1
PIN_CONFIG_BIAS_PULL_DOWNPull-down direnci etkinleştirDirenç değeri veya 1
PIN_CONFIG_BIAS_DISABLEPull-up/down devre dışı
PIN_CONFIG_BIAS_HIGH_IMPEDANCEHi-Z (yüksek empedans)
PIN_CONFIG_DRIVE_STRENGTHÇıkış akımı kapasitesimA cinsinden (4, 8, 12, 16…)
PIN_CONFIG_SLEW_RATEKenar hızı sınırı0=yavaş, 1=hızlı
PIN_CONFIG_INPUT_SCHMITT_ENABLESchmitt trigger etkinleştir1=etkin
PIN_CONFIG_OUTPUTÇıkış modunda başlat0=low, 1=high
PIN_CONFIG_INPUT_ENABLEGiriş buffer'ı etkinleştir

pinconf_ops implementasyonu

my_pinctrl.c — pinconf_ops
/* Pin konfigürasyon register offset'leri */
#define CONF_REG(pin)       (priv->base + 0x200 + (pin) * 4)
#define CONF_PULL_MASK      GENMASK(3, 2)
#define CONF_PULL_NONE      0
#define CONF_PULL_UP        1
#define CONF_PULL_DOWN      2
#define CONF_DRV_MASK       GENMASK(6, 4)  /* 0=4mA, 1=8mA, 2=12mA, 3=16mA */
#define CONF_SLEW_BIT       BIT(7)
#define CONF_SCHMITT_BIT    BIT(8)

static int my_pinconf_get(struct pinctrl_dev  *pctldev,
                           unsigned int         pin,
                           unsigned long       *config)
{
    struct my_pinctrl_priv *priv = pinctrl_dev_get_drvdata(pctldev);
    enum pin_config_param  param = pinconf_to_config_param(*config);
    u32 reg_val = readl(CONF_REG(pin));
    u32 arg = 0;

    switch (param) {
    case PIN_CONFIG_BIAS_PULL_UP:
        if (FIELD_GET(CONF_PULL_MASK, reg_val) != CONF_PULL_UP)
            return -EINVAL;
        arg = 1;
        break;
    case PIN_CONFIG_BIAS_PULL_DOWN:
        if (FIELD_GET(CONF_PULL_MASK, reg_val) != CONF_PULL_DOWN)
            return -EINVAL;
        arg = 1;
        break;
    case PIN_CONFIG_BIAS_DISABLE:
        arg = (FIELD_GET(CONF_PULL_MASK, reg_val) == CONF_PULL_NONE);
        break;
    case PIN_CONFIG_DRIVE_STRENGTH:
        arg = (FIELD_GET(CONF_DRV_MASK, reg_val) + 1) * 4;  /* mA */
        break;
    case PIN_CONFIG_SLEW_RATE:
        arg = !!(reg_val & CONF_SLEW_BIT);
        break;
    case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
        arg = !!(reg_val & CONF_SCHMITT_BIT);
        break;
    default:
        return -ENOTSUPP;
    }

    *config = pinconf_to_config_packed(param, arg);
    return 0;
}

static int my_pinconf_set(struct pinctrl_dev  *pctldev,
                           unsigned int         pin,
                           unsigned long       *configs,
                           unsigned int         num_configs)
{
    struct my_pinctrl_priv *priv = pinctrl_dev_get_drvdata(pctldev);
    unsigned long           flags;
    u32                     reg_val;
    int                     i;

    spin_lock_irqsave(&priv->lock, flags);
    reg_val = readl(CONF_REG(pin));

    for (i = 0; i < num_configs; i++) {
        enum pin_config_param param = pinconf_to_config_param(configs[i]);
        u32                  arg   = pinconf_to_config_argument(configs[i]);

        switch (param) {
        case PIN_CONFIG_BIAS_PULL_UP:
            reg_val = (reg_val & ~CONF_PULL_MASK) |
                      FIELD_PREP(CONF_PULL_MASK, CONF_PULL_UP);
            break;
        case PIN_CONFIG_BIAS_PULL_DOWN:
            reg_val = (reg_val & ~CONF_PULL_MASK) |
                      FIELD_PREP(CONF_PULL_MASK, CONF_PULL_DOWN);
            break;
        case PIN_CONFIG_BIAS_DISABLE:
            reg_val = (reg_val & ~CONF_PULL_MASK) |
                      FIELD_PREP(CONF_PULL_MASK, CONF_PULL_NONE);
            break;
        case PIN_CONFIG_DRIVE_STRENGTH:
            /* arg: mA cinsinden; 4, 8, 12, 16 geçerli */
            {
                u32 drv = (arg / 4) - 1;
                drv = clamp_val(drv, 0, 3);
                reg_val = (reg_val & ~CONF_DRV_MASK) |
                          FIELD_PREP(CONF_DRV_MASK, drv);
            }
            break;
        case PIN_CONFIG_SLEW_RATE:
            if (arg)
                reg_val |= CONF_SLEW_BIT;
            else
                reg_val &= ~CONF_SLEW_BIT;
            break;
        case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
            if (arg)
                reg_val |= CONF_SCHMITT_BIT;
            else
                reg_val &= ~CONF_SCHMITT_BIT;
            break;
        default:
            spin_unlock_irqrestore(&priv->lock, flags);
            return -ENOTSUPP;
        }
    }

    writel(reg_val, CONF_REG(pin));
    spin_unlock_irqrestore(&priv->lock, flags);
    return 0;
}

static const struct pinconf_ops my_pinconf_ops = {
    .is_generic  = true,       /* generic DT parsing kullan */
    .pin_config_get  = my_pinconf_get,
    .pin_config_set  = my_pinconf_set,
    .pin_config_group_set = my_pinconf_group_set,
};

Bu bölümde

  • pinconf_to_config_param / pinconf_to_config_argument: packed config encoding'i çöz
  • is_generic = true: DT'deki bias-pull-up, drive-strength vb. otomatik parse edilir
  • spin_lock_irqsave + readl/writel: register R-M-W atomik erişim
  • pin_config_group_set: tüm gruba aynı konfigürasyonu uygulama

04 Device Tree pinctrl binding — pinctrl-names, pinctrl-0

Consumer cihazlarının DT'de pinctrl state'lerini nasıl tanımladığı ve framework'ün bunları otomatik uygulama mekanizması.

pinctrl-names ve pinctrl-N property'leri

my-board.dts — consumer pinctrl binding
&uart0 {
    status = "okay";

    /*
     * pinctrl-names: state isimlerinin listesi
     * pinctrl-0:     "default" state için pin konfigürasyonları
     * pinctrl-1:     "sleep"   state için pin konfigürasyonları
     *
     * Framework, probe() sırasında otomatik olarak "default" state'i uygular.
     * suspend sırasında "sleep" state'i uygulanır (driver destekliyorsa).
     */
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart0_pins_default>;
    pinctrl-1 = <&uart0_pins_sleep>;
};

&spi1 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&spi1_pins>;
};

&i2c0 {
    status = "okay";
    /* Yalnızca default state — sleep state yok */
    pinctrl-names = "default";
    pinctrl-0 = <&i2c0_pins>;
};

Pin konfigürasyon node'larının tanımlanması

my-soc.dtsi — pinctrl node ve konfigürasyonlar
pinctrl: pinctrl@11000000 {
    compatible = "myvendor,mysoc-pinctrl";
    reg = <0x11000000 0x2000>;

    uart0_pins_default: uart0-default {
        pins = "PA2", "PA3", "PA4", "PA5";
        function = "uart0";
        bias-pull-up;
        drive-strength = <8>;     /* 8 mA */
    };

    uart0_pins_sleep: uart0-sleep {
        pins = "PA2", "PA3", "PA4", "PA5";
        function = "gpio";         /* GPIO moduna al */
        bias-pull-down;            /* floating önle */
        output-low;                /* low çek */
    };

    spi1_pins: spi1-default {
        /* CLK, MOSI, MISO ayrı konfigürasyon */
        spi1-clk-mosi {
            pins = "PB2", "PB3";
            function = "spi1";
            drive-strength = <12>; /* yüksek hız */
            slew-rate = <1>;       /* hızlı kenar */
        };
        spi1-miso {
            pins = "PB4";
            function = "spi1";
            bias-pull-up;
            input-schmitt-enable;
        };
    };

    i2c0_pins: i2c0-default {
        pins = "PC4", "PC5";
        function = "i2c0";
        bias-pull-up;
        drive-strength = <4>;
    };
};

Framework'ün otomatik uygulaması

drivers/base/dd.c — otomatik state uygulama (kavramsal)
/*
 * Framework, driver probe() çağrılmadan önce "default" state'i uygular.
 * Sürücü pinctrl_select_state() çağırmak zorunda değildir.
 *
 * Sürücü manuel state seçimi yapmak istiyorsa:
 */
static int my_uart_probe(struct platform_device *pdev)
{
    struct pinctrl       *pc;
    struct pinctrl_state *sleep_state;

    /* Framework default state'i zaten uyguladı.
     * Ama sleep state handle'ını saklayabiliriz: */
    pc = devm_pinctrl_get(&pdev->dev);
    if (IS_ERR(pc))
        return dev_err_probe(&pdev->dev, PTR_ERR(pc),
                             "pinctrl alinamadi\n");

    sleep_state = pinctrl_lookup_state(pc, PINCTRL_STATE_SLEEP);
    if (IS_ERR(sleep_state))
        sleep_state = NULL;  /* sleep state isteğe bağlı */

    /* Suspend callback'inde: */
    if (sleep_state)
        pinctrl_select_state(pc, sleep_state);

    return 0;
}
pinctrl-names = "default"Zorunlu; framework probe öncesi otomatik uygular
pinctrl-names = "sleep"Opsiyonel; suspend sırasında framework veya sürücü seçer
pinctrl-names = "idle"Opsiyonel; düşük aktivite durumu için; sürücü manuel seçer
pinctrl-0 / pinctrl-1 / ...pinctrl-names sırasına karşılık gelen konfigürasyon phandle listesi

Bu bölümde

  • pinctrl-names + pinctrl-N: sıra eşleşmeli; "default" framework tarafından otomatik uygulanır
  • Alt node'lar: pins, function, bias-pull-up/down, drive-strength, slew-rate
  • Aynı node içinde birden fazla alt grup: farklı elektriksel konfigürasyonlar için
  • devm_pinctrl_get + pinctrl_lookup_state: sürücü taraflı manuel state yönetimi

05 Generic pinctrl DT binding — pin-controller, pinmux, pinconf

Vendor-bağımsız "generic" DT pinctrl binding standardı ve eski vendor-specific binding ile karşılaştırma.

Linux 3.14 ile birlikte "generic pinctrl DT binding" standardize edildi. Bu binding, pinmux ve pinconf property'lerini pin numarası bazlı tanımlar; vendor-specific property adları yerine standart parametreler kullanılır. Yeni SoC sürücüleri bu binding'i kullanmalıdır.

Generic binding örneği

my-soc.dtsi — generic pinctrl binding
pinctrl: pin-controller@11000000 {
    compatible = "myvendor,mysoc-pinctrl";
    reg = <0x11000000 0x2000>;

    /*
     * Generic binding: pinmux = <pin_id mux_selector ...>
     * PIN_FUNC_ID(pin, func) makrosu veya elle sayısal değer
     *
     * my_soc_iomux.h dosyasında:
     *   #define MY_PIN_PA2_UART0 (2 | (1 << 8))   // pin 2, func 1
     */

    uart0_pins_default: uart0-default {
        pinmux = <MY_PIN_PA2_UART0   /* PA2 = UART0_TX */
                   MY_PIN_PA3_UART0   /* PA3 = UART0_RX */
                   MY_PIN_PA4_UART0   /* PA4 = UART0_CTS */
                   MY_PIN_PA5_UART0>; /* PA5 = UART0_RTS */
        bias-pull-up;
        drive-strength-microamp = <8000>;  /* 8 mA */
    };

    /* GPIO konfigürasyonu da generic binding ile */
    gpios_default: gpios-default {
        pinmux = <MY_PIN_PC0_GPIO
                   MY_PIN_PC1_GPIO>;
        bias-pull-down;
    };

    /* Sadece pinconf (fonksiyon yok, sadece elektrik) */
    i2c0_pins: i2c0-default {
        pinmux = <MY_PIN_PC4_I2C0_SCL
                   MY_PIN_PC5_I2C0_SDA>;
        drive-open-drain;   /* I2C için open-drain */
        bias-pull-up;
    };
};

Vendor-specific vs generic binding karşılaştırması

ÖzellikVendor-specificGeneric binding
Mux tanımıfunction = "uart0"; pins = "PA2"pinmux = <PIN_FUNC_ID>
Pull konfigürasyonumyvendor,pull-mode = <1>bias-pull-up / bias-pull-down
Drive strengthmyvendor,drive-strength = <2>drive-strength = <8> (mA)
DT parsingdt_node_to_map özel implementasyonpinconf_generic_dt_node_to_map_all
TaşınabilirlikVendor'a özgü, belgelenmesi zorStandart; tüm araçlar tanır
BakımHer SoC farklı property adlarıTek standart; yeni sürücüler için tercih edilmeli

dt_node_to_map_all ile otomatik parsing

my_pinctrl.c — generic DT parsing
/*
 * pinconf_generic_dt_node_to_map_all kullanılıyorsa
 * özel dt_node_to_map implementasyonu gerekmez.
 *
 * is_generic = true ile pinconf_ops tanımlanır ve
 * DT'deki bias-pull-up, drive-strength vb. parametreler
 * otomatik olarak PIN_CONFIG_* enum'larına dönüştürülür.
 */
static const struct pinctrl_ops my_pctrl_ops = {
    .get_groups_count = my_get_groups_count,
    .get_group_name   = my_get_group_name,
    .get_group_pins   = my_get_group_pins,
    .dt_node_to_map   = pinconf_generic_dt_node_to_map_all,
    .dt_free_map      = pinctrl_utils_free_map,
};

/*
 * pinmux = <pin_id> için PIN_FUNC_ID makrosu örneği
 * (vendor header dosyasında tanımlanır):
 */
/* my_soc_iomux.h */
#define MY_PINMUX(pin, func) ((pin) | ((func) << 8))
#define MY_PIN_PA2_GPIO      MY_PINMUX(2, 0)
#define MY_PIN_PA2_UART0_TX  MY_PINMUX(2, 1)
#define MY_PIN_PA2_SPI1_MOSI MY_PINMUX(2, 2)
NOT

Generic pinctrl binding'de pinmux property'si, pin ID ve fonksiyon ID'yi tek bir 32-bit değerde kodlar. Exact encoding SoC'a özgüdür (vendordan PINMUX makrosu alınır), ancak DT property adı standarttır. Yeni kernel sürücülerinde pinconf_generic_dt_node_to_map_all kullanmak kodunuzu önemli ölçüde kısaltır.

Bu bölümde

  • Generic binding: pinmux = <PIN_FUNC_ID>, bias-pull-up, drive-strength — standart property'ler
  • pinconf_generic_dt_node_to_map_all: vendor dt_node_to_map yazmaktan kurtarır
  • Vendor-specific vs generic: yeni sürücülerde generic tercih edilmeli
  • MY_PINMUX(pin, func): encoding vendor header'da, DT property adı standart

06 GPIO + pinctrl entegrasyonu — gpiochip_add_pin_range

pinctrl ve GPIO alt sistemlerinin entegrasyonu: GPIO talep edildiğinde pin otomatik olarak GPIO moduna alınır.

Linux GPIO alt sistemi ve pinctrl alt sistemi birbirine bağlıdır: gpiod_request() çağrıldığında pinctrl, ilgili pini GPIO fonksiyonuna geçirir. Bu entegrasyon gpiochip_add_pin_range() çağrısıyla kurulur; ardından gpio_request_enable callback otomatik tetiklenir.

gpio_chip ve pinctrl entegrasyonu

my_pinctrl.c — gpio_chip entegrasyonu
#include <linux/gpio/driver.h>

static int my_gpio_direction_input(struct gpio_chip *gc,
                                    unsigned int      offset)
{
    struct my_pinctrl_priv *priv = gpiochip_get_data(gc);
    u32 reg = readl(priv->base + GPIO_DIR_REG);
    reg &= ~BIT(offset);  /* 0 = input */
    writel(reg, priv->base + GPIO_DIR_REG);
    return 0;
}

static int my_gpio_direction_output(struct gpio_chip *gc,
                                     unsigned int      offset,
                                     int               value)
{
    struct my_pinctrl_priv *priv = gpiochip_get_data(gc);
    u32 reg;

    /* Önce değeri ayarla, sonra output'a çevir (glitch önleme) */
    reg = readl(priv->base + GPIO_DATA_REG);
    if (value)
        reg |= BIT(offset);
    else
        reg &= ~BIT(offset);
    writel(reg, priv->base + GPIO_DATA_REG);

    reg = readl(priv->base + GPIO_DIR_REG);
    reg |= BIT(offset);   /* 1 = output */
    writel(reg, priv->base + GPIO_DIR_REG);
    return 0;
}

static int my_gpio_get(struct gpio_chip *gc, unsigned int offset)
{
    struct my_pinctrl_priv *priv = gpiochip_get_data(gc);
    return !!(readl(priv->base + GPIO_DATA_REG) & BIT(offset));
}

static void my_gpio_set(struct gpio_chip *gc,
                         unsigned int      offset,
                         int               value)
{
    struct my_pinctrl_priv *priv = gpiochip_get_data(gc);
    u32 reg = readl(priv->base + GPIO_DATA_REG);
    if (value)
        reg |= BIT(offset);
    else
        reg &= ~BIT(offset);
    writel(reg, priv->base + GPIO_DATA_REG);
}

static int my_gpio_request(struct gpio_chip *gc, unsigned int offset)
{
    /* pinctrl entegrasyonu: gpiochip_generic_request yeterli */
    return gpiochip_generic_request(gc, offset);
}

static void my_gpio_free(struct gpio_chip *gc, unsigned int offset)
{
    gpiochip_generic_free(gc, offset);
}

static int my_pinctrl_probe(struct platform_device *pdev)
{
    struct my_pinctrl_priv *priv = /* ... init ... */
    int ret;

    /* gpio_chip tanımı */
    priv->gc.label            = "my-soc-gpio";
    priv->gc.parent           = &pdev->dev;
    priv->gc.owner            = THIS_MODULE;
    priv->gc.base             = -1;       /* otomatik numaralandırma */
    priv->gc.ngpio            = MY_GPIO_COUNT;
    priv->gc.of_node          = pdev->dev.of_node;
    priv->gc.request          = my_gpio_request;
    priv->gc.free             = my_gpio_free;
    priv->gc.direction_input  = my_gpio_direction_input;
    priv->gc.direction_output = my_gpio_direction_output;
    priv->gc.get              = my_gpio_get;
    priv->gc.set              = my_gpio_set;

    ret = devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
    if (ret)
        return dev_err_probe(&pdev->dev, ret,
                             "gpiochip kaydi basarisiz\n");

    /* pinctrl'i kaydet */
    priv->pctldev = devm_pinctrl_register(&pdev->dev,
                                           &my_pinctrl_desc, priv);
    if (IS_ERR(priv->pctldev))
        return PTR_ERR(priv->pctldev);

    /* GPIO ve pinctrl'i bağla: GPIO 0..N-1 → pin 0..N-1 eşleştirmesi */
    ret = gpiochip_add_pin_range(&priv->gc,
                                  dev_name(&pdev->dev),
                                  0,           /* gpio offset */
                                  0,           /* pin offset */
                                  MY_GPIO_COUNT);
    if (ret)
        dev_warn(&pdev->dev, "pin range eklenemedi: %d\n", ret);

    return 0;
}
ÖNEMLİ

gpiochip_add_pin_range() çağrısının ardından gpiod_request() çağrıldığında pinctrl otomatik olarak gpio_request_enable callback'ini tetikler — bu da pini GPIO moduna alır. gpiochip_generic_request() kullanıldığında bu bağlantı otomatik kurulur.

Bu bölümde

  • gpiochip_add_pin_range(): GPIO offset → pinctrl pin ID eşleştirmesi
  • gpiochip_generic_request / free: pinctrl gpio_request_enable callback'ini tetikler
  • gc.base = -1: GPIO numarası otomatik atanır (dinamik GPIO)
  • Entegrasyon sonrası: gpiod_request → pin GPIO fonksiyonuna otomatik geçer

07 Durum yönetimi — default, sleep, idle state geçişleri

Pinctrl state'lerinin yaşam döngüsü: framework otomasyonu ve sürücü taraflı manuel state geçişleri.

State yaşam döngüsü

  probe()
    |
    v
  [default state uygulanır]  ← framework otomatik yapar
    |                           (pinctrl-names[0] = "default")
    v
  Normal çalışma
    |
    +-----------+
    |           |
  suspend()  runtime idle
    |           |
    v           v
  [sleep]    [idle]      ← sürücü manuel seçer
    |
    v
  resume()
    |
    v
  [default state geri]   ← framework otomatik geri getirir
    |
    v
  remove()
    |
    v
  [state kaldırılır]
    

Sürücü taraflı state yönetimi

my_uart.c — suspend/resume state geçişi
struct my_uart_priv {
    struct pinctrl       *pc;
    struct pinctrl_state *default_state;
    struct pinctrl_state *sleep_state;
    struct pinctrl_state *idle_state;
    /* ... */
};

static int my_uart_probe(struct platform_device *pdev)
{
    struct my_uart_priv *priv;
    int ret;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->pc = devm_pinctrl_get(&pdev->dev);
    if (IS_ERR(priv->pc))
        return dev_err_probe(&pdev->dev, PTR_ERR(priv->pc),
                             "pinctrl alinamadi\n");

    /* default state — framework zaten uyguladı, handle'ı sakla */
    priv->default_state = pinctrl_lookup_state(priv->pc,
                                                PINCTRL_STATE_DEFAULT);
    if (IS_ERR(priv->default_state))
        return dev_err_probe(&pdev->dev,
                             PTR_ERR(priv->default_state),
                             "default state bulunamadi\n");

    /* sleep state — opsiyonel */
    priv->sleep_state = pinctrl_lookup_state(priv->pc,
                                              PINCTRL_STATE_SLEEP);
    if (IS_ERR(priv->sleep_state))
        priv->sleep_state = NULL;

    /* idle state — opsiyonel */
    priv->idle_state = pinctrl_lookup_state(priv->pc, "idle");
    if (IS_ERR(priv->idle_state))
        priv->idle_state = NULL;

    platform_set_drvdata(pdev, priv);
    return 0;
}

static int my_uart_suspend(struct device *dev)
{
    struct my_uart_priv *priv = dev_get_drvdata(dev);

    if (priv->sleep_state)
        return pinctrl_select_state(priv->pc, priv->sleep_state);

    return 0;
}

static int my_uart_resume(struct device *dev)
{
    struct my_uart_priv *priv = dev_get_drvdata(dev);
    return pinctrl_select_state(priv->pc, priv->default_state);
}

/* Runtime PM için idle state kullanımı */
static int my_uart_runtime_suspend(struct device *dev)
{
    struct my_uart_priv *priv = dev_get_drvdata(dev);

    if (priv->idle_state)
        pinctrl_select_state(priv->pc, priv->idle_state);

    return 0;
}

static const struct dev_pm_ops my_uart_pm_ops = {
    SET_SYSTEM_SLEEP_PM_OPS(my_uart_suspend, my_uart_resume)
    SET_RUNTIME_PM_OPS(my_uart_runtime_suspend,
                       my_uart_runtime_resume, NULL)
};

State isimlerinin standartları

State ismiMakro sabitiUygulama zamanı
"default"PINCTRL_STATE_DEFAULTprobe() öncesi framework otomasyonu
"sleep"PINCTRL_STATE_SLEEPsystem suspend — enerji tasarrufu
"idle"(string sabit)runtime PM idle — düşük aktivite
"init"(string sabit)bootloader sonrası ilk init
GÜÇ TASARRUFU

Uyku sırasında pin'leri GPIO moduna alıp pull-down yapmak sleep state'inin en yaygın kullanım amacıdır. Floating pin'ler yüksek frekansla toggling yaparak önemli uyku akımı tüketimine yol açabilir — özellikle uzun hat veya kapasitif yük olan pin'lerde bu 50–200 µA düzeyinde fazladan akıma neden olur.

Bu bölümde

  • default: framework tarafından probe öncesi otomatik uygulanır
  • sleep: system suspend callback'inde sürücü manuel seçer; floating önleme
  • idle: runtime PM için — periyodik aktivite olmayan dönemlerde akım tasarrufu
  • pinctrl_lookup_state + pinctrl_select_state: sürücü taraflı state geçiş API

08 Hata ayıklama — debugfs pinctrl/, pin_dump

/sys/kernel/debug/pinctrl/ arayüzü ve çalışan sistemde pin konfigürasyonlarını doğrulama yöntemleri.

debugfs pinctrl/ dosyaları

bash — debugfs pinctrl/ keşfi
# debugfs mount
mount -t debugfs none /sys/kernel/debug

# Tüm pinctrl controller'larını listele
ls /sys/kernel/debug/pinctrl/
# Çıktı: my-soc-pinctrl  pinctrl-maps

# Controller içindeki dosyalar
ls /sys/kernel/debug/pinctrl/my-soc-pinctrl/
# gpio-ranges  pingroups  pinmux-functions  pinmux-pins  pins

# Tüm pin'lerin listesi ve ID'leri
cat /sys/kernel/debug/pinctrl/my-soc-pinctrl/pins
# Çıktı:
# registered pins: 128
# pin 0 (PA0) my-soc-pinctrl
# pin 1 (PA1) my-soc-pinctrl
# pin 2 (PA2) my-soc-pinctrl
# ...

# Pin grupları
cat /sys/kernel/debug/pinctrl/my-soc-pinctrl/pingroups
# Çıktı:
# registered pin groups:
# group: uart0_grp
# pin 2 (PA2)
# pin 3 (PA3)
# pin 4 (PA4)
# pin 5 (PA5)

# Fonksiyon listesi
cat /sys/kernel/debug/pinctrl/my-soc-pinctrl/pinmux-functions
# Çıktı:
# function: uart0, groups = [ uart0_grp ]
# function: spi1,  groups = [ spi1_grp  ]

pinmux-pins — hangi pin neye bağlanmış?

bash — pinmux-pins ve pin_dump
# Mux edilmiş pin'ler — en yararlı debug çıktısı
cat /sys/kernel/debug/pinctrl/my-soc-pinctrl/pinmux-pins
# Çıktı:
# Pinmux settings per pin
# Format: pin (name): mux_owner gpio_owner hog?
# pin 2 (PA2): uart0 (GPIO UNCLAIMED) function uart0 group uart0_grp
# pin 3 (PA3): uart0 (GPIO UNCLAIMED) function uart0 group uart0_grp
# pin 10 (PB2): spi1 (GPIO UNCLAIMED) function spi1 group spi1_grp
# pin 7 (PA7): (MUX UNCLAIMED) (GPIO UNCLAIMED)

# GPIO range bilgisi
cat /sys/kernel/debug/pinctrl/my-soc-pinctrl/gpio-ranges
# Çıktı:
# GPIO ranges handled:
# 0: my-soc-gpio GPIOS [480 - 607] PINS [0 - 127]

# pinctrl-maps: tüm device-state eşleştirmeleri
cat /sys/kernel/debug/pinctrl/pinctrl-maps
# Çıktı:
# Pinctrl maps:
# device uart0
# state default
# type MUX_GROUP (2)
# controlling device my-soc-pinctrl
# group uart0_grp
# function uart0

Yaygın sorunlar ve çözümleri

BelirtiOlası nedenKontrol
-EPROBE_DEFERpinctrl controller sürücüsü yüklenmemişdmesg; pinctrl compatible string DTS eşleşmesi
-EBUSY pin talep hatasıBaşka sürücü pini mux etmiş (strict=true)pinmux-pins dosyası; mux_owner alanı
default state uygulanamıyorDTS'te pinctrl-names / pinctrl-0 eksikDTS doğrulama; pinctrl-maps çıktısı
GPIO çalışmıyorgpiochip_add_pin_range eksikgpio-ranges dosyası; pin GPIO modunda mı?
set_mux başarısızfunc_selector geçersiz aralıktapinmux-functions listesi; sürücü array sınırı
Pull-up etkisizpinconf_ops is_generic eksikis_generic=true; pinconf_generic_* helpers

Kernel config gereksinimleri

Kconfig — pinctrl
CONFIG_PINCTRL=y
CONFIG_PINMUX=y
CONFIG_PINCONF=y
CONFIG_GENERIC_PINCTRL_GROUPS=y
CONFIG_GENERIC_PINMUX_FUNCTIONS=y
CONFIG_GENERIC_PINCONF=y
CONFIG_DEBUG_FS=y
# Vendor-specific sürücü
# CONFIG_PINCTRL_MYSOC=y

Pin state doğrulama scripti

bash — pin state doğrulama
#!/bin/sh
# Pin durumunu kontrol et
PCTRL=/sys/kernel/debug/pinctrl/my-soc-pinctrl

# UART0 pinlerinin beklenen mux'ta olduğunu doğrula
for pin in "PA2" "PA3" "PA4" "PA5"; do
    result=$(grep -w $pin $PCTRL/pinmux-pins)
    if echo $result | grep -q "uart0"; then
        echo "OK: $pin -> uart0"
    else
        echo "HATA: $pin uart0'a baglanmamis: $result"
    fi
done

# Talep edilmemiş kritik pinleri bul
echo "Talep edilmemis pinler:"
grep "UNCLAIMED.*UNCLAIMED" $PCTRL/pinmux-pins | head -20

Bu bölümde

  • pins: pin ID ve isim listesi; pingroups: her gruptaki pin'ler
  • pinmux-pins: mux_owner + gpio_owner — en kritik debug çıktısı
  • pinctrl-maps: device-state-group-function eşleştirmelerinin tam listesi
  • -EBUSY: strict=true ile çakışma tespiti; pinmux-pins ile owner belirleme
  • Uyku akımı: unclaimed floating pin'ler — pinmux-pins UNCLAIMED satırlarını takip et