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
| Sorun | Açı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 sorunu | Uyku 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 |
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ı
#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ı
/* 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
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ı
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
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 */
};
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 enum | Anlamı | Tipik değer |
|---|---|---|
| PIN_CONFIG_BIAS_PULL_UP | Pull-up direnci etkinleştir | Direnç değeri (Ohm) veya 1 |
| PIN_CONFIG_BIAS_PULL_DOWN | Pull-down direnci etkinleştir | Direnç değeri veya 1 |
| PIN_CONFIG_BIAS_DISABLE | Pull-up/down devre dışı | — |
| PIN_CONFIG_BIAS_HIGH_IMPEDANCE | Hi-Z (yüksek empedans) | — |
| PIN_CONFIG_DRIVE_STRENGTH | Çıkış akımı kapasitesi | mA cinsinden (4, 8, 12, 16…) |
| PIN_CONFIG_SLEW_RATE | Kenar hızı sınırı | 0=yavaş, 1=hızlı |
| PIN_CONFIG_INPUT_SCHMITT_ENABLE | Schmitt trigger etkinleştir | 1=etkin |
| PIN_CONFIG_OUTPUT | Çıkış modunda başlat | 0=low, 1=high |
| PIN_CONFIG_INPUT_ENABLE | Giriş buffer'ı etkinleştir | — |
pinconf_ops implementasyonu
/* 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
&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ı
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ı
/*
* 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;
}
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
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ı
| Özellik | Vendor-specific | Generic binding |
|---|---|---|
| Mux tanımı | function = "uart0"; pins = "PA2" | pinmux = <PIN_FUNC_ID> |
| Pull konfigürasyonu | myvendor,pull-mode = <1> | bias-pull-up / bias-pull-down |
| Drive strength | myvendor,drive-strength = <2> | drive-strength = <8> (mA) |
| DT parsing | dt_node_to_map özel implementasyon | pinconf_generic_dt_node_to_map_all |
| Taşınabilirlik | Vendor'a özgü, belgelenmesi zor | Standart; tüm araçlar tanır |
| Bakım | Her SoC farklı property adları | Tek standart; yeni sürücüler için tercih edilmeli |
dt_node_to_map_all ile otomatik 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)
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
#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;
}
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
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 ismi | Makro sabiti | Uygulama zamanı |
|---|---|---|
| "default" | PINCTRL_STATE_DEFAULT | probe() öncesi framework otomasyonu |
| "sleep" | PINCTRL_STATE_SLEEP | system suspend — enerji tasarrufu |
| "idle" | (string sabit) | runtime PM idle — düşük aktivite |
| "init" | (string sabit) | bootloader sonrası ilk init |
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ı
# 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ış?
# 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
| Belirti | Olası neden | Kontrol |
|---|---|---|
| -EPROBE_DEFER | pinctrl 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ıyor | DTS'te pinctrl-names / pinctrl-0 eksik | DTS doğrulama; pinctrl-maps çıktısı |
| GPIO çalışmıyor | gpiochip_add_pin_range eksik | gpio-ranges dosyası; pin GPIO modunda mı? |
| set_mux başarısız | func_selector geçersiz aralıkta | pinmux-functions listesi; sürücü array sınırı |
| Pull-up etkisiz | pinconf_ops is_generic eksik | is_generic=true; pinconf_generic_* helpers |
Kernel config gereksinimleri
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
#!/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