00 Platform bus nedir
Platform bus, keşfedilemeyen (non-discoverable) memory-mapped peripheral'lar için Linux driver modelinin sunduğu sanal bus'tur.
Linux driver modeli, her donanım bileşenini bir bus üzerinden yönetir. I2C, SPI veya USB gibi bus'lar kendi protokolleriyle cihazları otomatik keşfedebilir. Ancak SoC içindeki UART, GPIO, timer gibi memory-mapped peripheral'lar keşfedilemez — bunların tanımlanması gerekir. İşte platform bus bu boşluğu doldurur: Device Tree üzerinden tanımlanan peripheral'lar için platform_device nesneleri oluşturulur ve bu nesneler platform_driver nesneleriyle eşleştirilir.
Device Tree (.dts)
┌──────────────────────────────────────┐
│ mydev@10000000 { │
│ compatible = "myvendor,mydev"; │
│ reg = <0x10000000 0x1000>; │
│ interrupts = <0 42 4>; │
│ }; │
└──────────────┬───────────────────────┘
│ of_platform_populate()
▼
platform_device ←── platform bus ──→ platform_driver
(kernel oluşturur) (modül yükler)
│ compatible string eşleşirse
▼
probe() çağrılır
Bus tipleri karşılaştırması
| Bus tipi | Struct | Keşfedilebilir? | Tipik kullanım |
|---|---|---|---|
| platform | platform_driver | Hayır (DT/ACPI) | SoC peripheral: UART, GPIO, timer, SPI controller |
| i2c | i2c_driver | Kısmi (DT) | I2C slave: sensör, EEPROM, RTC |
| spi | spi_driver | Kısmi (DT) | SPI slave: flash, ADC, display |
| pci | pci_driver | Evet | PCIe kartlar: NIC, GPU, NVMe |
| usb | usb_driver | Evet | USB aygıtlar: kamera, HID, depolama |
Platform bus'ta device → driver eşleşmesi compatible string üzerinden yapılır. DTS'teki compatible = "myvendor,mydev" değeri, driver'ın of_match_table dizisindeki bir .compatible alanıyla örtüşmezse probe() hiç çağrılmaz.
Bu bölümde
- Platform bus: non-discoverable memory-mapped peripheral'lar için sanal bus
- of_platform_populate(): DT node → platform_device otomasyonu
- platform_device + platform_driver çifti; compatible string eşleşmesi
- I2C/SPI/PCI/USB bus'larıyla karşılaştırma — hangi senaryoda hangisi
- module_platform_driver() — tek satır register/unregister makrosu
01 Driver kaydı ve of_match_table
platform_driver struct'ının anatomisi ve Device Tree compatible string eşleştirme mekanizması.
Bir platform driver'ın kalbi struct platform_driver'dır. Bu struct içindeki .driver.of_match_table alanı, hangi Device Tree node'larıyla eşleşileceğini belirler. Tablo NULL sentinel ile sonlandırılmalıdır.
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
MODULE_DESCRIPTION("Platform driver skeleton");
/*
* of_device_id tablosu: DT'deki compatible degerleriyle eslesir
* Son satir bos olmali (sentinel)
*/
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "myvendor,mydev-v1" },
{ .compatible = "myvendor,mydev-v2", .data = (void *)2 },
{ } /* sentinel */
};
MODULE_DEVICE_TABLE(of, mydev_of_match);
static int mydev_probe(struct platform_device *pdev);
static int mydev_remove(struct platform_device *pdev);
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.remove = mydev_remove,
.driver = {
.name = "mydev",
.of_match_table = mydev_of_match,
.owner = THIS_MODULE,
},
};
static int mydev_probe(struct platform_device *pdev)
{
const void *match_data = of_device_get_match_data(&pdev->dev);
int version = match_data ? (int)(uintptr_t)match_data : 1;
dev_info(&pdev->dev, "probe çağrıldı, hw versiyon=%d\n", version);
return 0;
}
static int mydev_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "remove çağrıldı\n");
return 0;
}
/*
* module_platform_driver():
* module_init → platform_driver_register(&mydev_driver)
* module_exit → platform_driver_unregister(&mydev_driver)
*/
module_platform_driver(mydev_driver);
of_match_table ile birden fazla chip versiyonu
Aynı driver farklı chip revizyonlarını destekleyebilir. .data alanına versiyon numarası veya konfigürasyon struct pointer'ı konulur:
struct mydev_config {
unsigned int fifo_depth;
bool has_dma;
};
static const struct mydev_config cfg_v1 = { .fifo_depth = 16, .has_dma = false };
static const struct mydev_config cfg_v2 = { .fifo_depth = 128, .has_dma = true };
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "myvendor,mydev-v1", .data = &cfg_v1 },
{ .compatible = "myvendor,mydev-v2", .data = &cfg_v2 },
{ }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);
static int mydev_probe(struct platform_device *pdev)
{
const struct mydev_config *cfg =
of_device_get_match_data(&pdev->dev);
dev_info(&pdev->dev, "FIFO depth=%u, DMA=%s\n",
cfg->fifo_depth, cfg->has_dma ? "evet" : "hayir");
return 0;
}
MODULE_DEVICE_TABLE ve modprobe autoloading
# depmod çalıştır → modules.alias güncelle
$ sudo depmod -a
# alias içeriğini kontrol et
$ grep myvendor /lib/modules/$(uname -r)/modules.alias
alias of:N*T*Cmyvendor,mydev-v1* mydev_driver
alias of:N*T*Cmyvendor,mydev-v2* mydev_driver
# Driver dosyasını kopyala ve modprobe ile yükle
$ sudo cp mydev_driver.ko /lib/modules/$(uname -r)/extra/
$ sudo modprobe mydev_driver
Bu bölümde
- struct platform_driver: .probe, .remove, .driver.name, .driver.of_match_table
- of_device_id tablosu: compatible string eşleştirme; .data ile versiyon bilgisi
- of_device_get_match_data() — eşleşen entry'nin .data pointer'ını al
- MODULE_DEVICE_TABLE(of, ...) — modprobe autoloading için modalias kaydı
- module_platform_driver() — tek satır register/unregister makrosu
02 probe() fonksiyonu
probe(), platform driver'ın kalbidir; device eşleşince çağrılır ve tüm kaynak tahsisi, başlatma ve kayıt işlemleri burada yapılır.
probe() başarıyla tamamlandığında device kullanıma hazırdır. Herhangi bir adım başarısız olursa negatif errno dönülür; kernel bu durumda devm_ cleanup mekanizmasıyla tahsis edilen kaynakları otomatik temizler.
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
struct mydev_priv {
void __iomem *base;
int irq;
u32 reg_shift;
const char *label;
};
static irqreturn_t mydev_irq(int irq, void *data)
{
return IRQ_HANDLED;
}
static int mydev_probe(struct platform_device *pdev)
{
struct mydev_priv *priv;
struct resource *res;
int ret;
/* 1. Private struct — device kaldırılınca otomatik kfree */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* 2. DT'den opsiyonel property'leri oku */
of_property_read_u32(pdev->dev.of_node, "reg-shift", &priv->reg_shift);
of_property_read_string(pdev->dev.of_node, "label", &priv->label);
if (!priv->label)
priv->label = "mydev";
/* 3. Register base address al ve ioremap et */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return dev_err_probe(&pdev->dev, -ENODEV,
"MEM resource bulunamadi\n");
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
dev_info(&pdev->dev, "registers: 0x%08llx mapped @ %p\n",
(unsigned long long)res->start, priv->base);
/* 4. IRQ numarası al */
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0)
return priv->irq;
/* 5. IRQ kaydet — device kaldırılınca otomatik free_irq */
ret = devm_request_irq(&pdev->dev, priv->irq, mydev_irq,
IRQF_TRIGGER_RISING, pdev->name, priv);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"IRQ %d kaydedilemedi\n", priv->irq);
/* 6. Private data'yı device'a bağla */
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "'%s' probe basarili, IRQ=%d\n",
priv->label, priv->irq);
return 0;
}
-EPROBE_DEFER dönen bir kaynak, henüz hazır olmayan başka bir driver tarafından sağlanıyor demektir. Kernel bu durumda probe'u daha sonra yeniden dener. dev_err_probe() bu errno için mesaj basmaz — log'u gereksiz gürültüden korur.
Bu bölümde
- probe() imzası: int probe(struct platform_device *pdev)
- devm_kzalloc → platform_get_resource → devm_ioremap_resource → platform_get_irq sırası
- of_property_read_u32() / of_property_read_string() ile DT property okuma
- dev_err_probe() — hata loglama + EPROBE_DEFER farkındalığı
- platform_set_drvdata() ile private struct'ın driver genelinde taşınması
03 remove() ve cleanup
devm_ kullanan modern driver'larda remove() genellikle boş ya da minimal; devm_ olmayan durumlarda ters sıralı manuel temizleme gerekir.
remove(), şu durumlarda çağrılır: modül kaldırma (rmmod), device tree overlay kaldırma, veya /sys/bus/platform/drivers/mydev/unbind yazma. devm_ kullanılmışsa kernel tüm registered cleanup fonksiyonlarını ters sırada çağırır.
/* ─── devm_ kullanan driver: remove neredeyse boş ─── */
static int mydev_remove_devm(struct platform_device *pdev)
{
/* devm_kzalloc, devm_ioremap_resource, devm_request_irq
* hepsi kernel tarafından otomatik temizlenir.
* Sadece devm_ ile yönetilemeyen şeyleri burada temizle. */
dev_info(&pdev->dev, "kaldirildi\n");
return 0;
}
/* ─── Manuel cleanup gereken driver ─── */
struct manual_priv {
void __iomem *base;
int irq;
struct clk *clk;
struct cdev cdev;
dev_t devno;
struct class *cls;
};
static int mydev_remove_manual(struct platform_device *pdev)
{
struct manual_priv *priv = platform_get_drvdata(pdev);
/* Temizleme sırası: yüklemenin TERSİ */
device_destroy(priv->cls, priv->devno);
class_destroy(priv->cls);
cdev_del(&priv->cdev);
unregister_chrdev_region(priv->devno, 1);
free_irq(priv->irq, priv);
clk_disable_unprepare(priv->clk);
clk_put(priv->clk);
iounmap(priv->base);
kfree(priv);
return 0;
}
Driver unbind/bind testi
# Device adını öğren
$ ls /sys/bus/platform/devices/ | grep mydev
10000000.mydev
# Manuel unbind → remove() çağrılır
$ echo "10000000.mydev" | sudo tee /sys/bus/platform/drivers/mydev/unbind
# Manuel bind → probe() çağrılır
$ echo "10000000.mydev" | sudo tee /sys/bus/platform/drivers/mydev/bind
devm_ kullandıysan remove()'da o kaynağı manuel temizleme — çift temizleme (double-free) veya use-after-free hatası oluşturur. devm_ ile yönetilen kaynaklar yalnızca kernel tarafından serbest bırakılır.
Bu bölümde
- devm_ kullanan driver'da remove() boş veya minimal — kernel otomatik temizler
- Manuel cleanup gerektiğinde ters sıra: device → irq → clk → ioremap → kfree
- platform_get_drvdata() ile remove'da private struct'a erişim
- /sys/bus/platform/drivers/mydev/unbind — bind/unbind ile probe/remove test
04 Device tree'den veri okuma
Device Tree property'lerini driver içinde okumak için of_ API'si kullanılır; her property tipinin kendine özgü okuma fonksiyonu vardır.
Device Tree node'una pdev->dev.of_node üzerinden erişilir. Tüm of_property_read_* fonksiyonları property varsa 0, yoksa negatif errno döner. Opsiyonel property'ler için dönüş değeri kontrol edilmeli; yoksa varsayılan değer kullanılmalıdır.
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
static int read_dt_properties(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
u32 val32;
u64 val64;
const char *str;
int gpio, irq, ret;
if (!np) {
dev_err(&pdev->dev, "device tree node yok\n");
return -ENODEV;
}
/* u32 property oku */
ret = of_property_read_u32(np, "reg-shift", &val32);
if (!ret)
dev_info(&pdev->dev, "reg-shift = %u\n", val32);
/* u64 property oku */
ret = of_property_read_u64(np, "clock-frequency", &val64);
if (!ret)
dev_info(&pdev->dev, "clock-freq = %llu Hz\n", val64);
/* string property oku */
ret = of_property_read_string(np, "label", &str);
if (!ret)
dev_info(&pdev->dev, "label = %s\n", str);
/* boolean property — sadece varlığını kontrol et */
if (of_property_read_bool(np, "hw-flow-control"))
dev_info(&pdev->dev, "HW flow control aktif\n");
/* GPIO numarası al (legacy binding) */
gpio = of_get_named_gpio(np, "reset-gpios", 0);
if (gpio_is_valid(gpio))
dev_info(&pdev->dev, "reset GPIO = %d\n", gpio);
/* IRQ numarası DT'den al */
irq = of_irq_get(np, 0);
if (irq > 0)
dev_info(&pdev->dev, "DT IRQ = %d\n", irq);
return 0;
}
DTS örneği
/ {
mydev: mydev@10000000 {
compatible = "myvendor,mydev-v2";
reg = <0x10000000 0x1000>;
interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>;
clocks = <&clk_apb>;
clock-names = "apb_pclk";
reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
reg-shift = <2>;
clock-frequency = <48000000>;
label = "mydev0";
hw-flow-control; /* boolean — değersiz */
status = "okay";
};
};
DT property → driver struct field eşlemesi
| DT property | of_ API | Driver struct field |
|---|---|---|
| reg | platform_get_resource(IORESOURCE_MEM) | void __iomem *base |
| interrupts | platform_get_irq(pdev, 0) | int irq |
| clocks / clock-names | devm_clk_get(&dev, "apb_pclk") | struct clk *clk |
| reset-gpios | devm_gpiod_get(&dev, "reset", GPIOD_OUT_LOW) | struct gpio_desc *reset |
| reg-shift | of_property_read_u32(np, "reg-shift", &v) | u32 reg_shift |
| label | of_property_read_string(np, "label", &s) | const char *label |
| hw-flow-control | of_property_read_bool(np, "hw-flow-control") | bool hw_flow |
Bu bölümde
- pdev->dev.of_node — DT node erişim noktası; !np kontrolü yap
- of_property_read_u32/u64/string() — tipli property okuma, ret kontrolü
- of_property_read_bool() — boolean/flag property varlık kontrolü
- of_get_named_gpio() — GPIO numarası; of_irq_get() — IRQ numarası
- DT property → of_ API → driver struct field mapping tablosu
05 GPIO ile LED driver
GPIO descriptor API'si ve sysfs attribute birleştirilerek DTS'ten yapılandırılan tam çalışan bir LED platform driver örneği.
/ {
my-leds {
compatible = "myvendor,my-leds";
gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
label = "status-led";
status = "okay";
};
};
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/sysfs.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
struct led_priv {
struct gpio_desc *gpiod;
const char *label;
int brightness;
};
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_priv *priv = dev_get_drvdata(dev);
return sysfs_emit(buf, "%d\n", priv->brightness);
}
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct led_priv *priv = dev_get_drvdata(dev);
int val, ret;
ret = kstrtoint(buf, 10, &val);
if (ret)
return ret;
priv->brightness = !!val;
/* GPIO_ACTIVE_LOW mantığı gpiod API tarafından uygulanır */
gpiod_set_value(priv->gpiod, priv->brightness);
dev_info(dev, "LED '%s' = %s\n", priv->label,
priv->brightness ? "ACIK" : "KAPALI");
return count;
}
static DEVICE_ATTR_RW(brightness);
static struct attribute *led_attrs[] = {
&dev_attr_brightness.attr,
NULL
};
static const struct attribute_group led_group = { .attrs = led_attrs };
static int led_probe(struct platform_device *pdev)
{
struct led_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/*
* GPIO descriptor al — DTS'teki gpios property'sinden
* NULL: "gpios" ozelligi (isimsiz)
* GPIOD_OUT_LOW: cikis olarak yapilandir, baslangic LOW
*/
priv->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(priv->gpiod))
return dev_err_probe(&pdev->dev, PTR_ERR(priv->gpiod),
"GPIO alinamadi\n");
of_property_read_string(pdev->dev.of_node, "label", &priv->label);
if (!priv->label)
priv->label = pdev->name;
platform_set_drvdata(pdev, priv);
/* sysfs attribute grubunu ekle — device kaldırılınca otomatik çıkar */
ret = devm_device_add_group(&pdev->dev, &led_group);
if (ret)
return ret;
dev_info(&pdev->dev, "LED '%s' hazir\n", priv->label);
return 0;
}
static int led_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "LED kaldirildi\n");
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "myvendor,my-leds" },
{ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "my-leds",
.of_match_table = led_of_match,
},
};
module_platform_driver(led_driver);
GPIO descriptor API
# LED'i aç
$ echo 1 | sudo tee /sys/bus/platform/devices/my-leds/brightness
# LED'i kapat
$ echo 0 | sudo tee /sys/bus/platform/devices/my-leds/brightness
# Mevcut durumu oku
$ cat /sys/bus/platform/devices/my-leds/brightness
1
Kernel'ın yerleşik LED subsystem'i (/sys/class/leds/) aynı işlevi daha zengin bir API ile sunar: CONFIG_LEDS_CLASS + devm_led_classdev_register(). Yeni LED driver yazıyorsan LED class kullanmayı değerlendir; buradaki örnek platform driver mekaniklerini göstermek içindir.
Bu bölümde
- devm_gpiod_get() — DTS'ten GPIO descriptor; polarite otomatik yönetilir
- gpiod_set_value() / gpiod_get_value() — ACTIVE_LOW/HIGH soyutlaması
- DEVICE_ATTR_RW + devm_device_add_group() — sysfs attribute ekleme
- Tam LED driver: DTS → probe → sysfs → GPIO kontrol döngüsü
- Kernel LED subsystem (/sys/class/leds/) — üretim kodu için alternatif
06 Interrupt ile register driver
Interrupt tabanlı peripheral okuma, workqueue ile deferred işleme ve wait queue mekanizmasıyla character device read() entegrasyonu.
Birçok peripheral, veri hazır olduğunda interrupt üretir. Driver bu interrupt'ı yakalayıp veriyi bir buffer'a kopyalar; userspace read() çağrısı ise bu buffer'dan okur. Okuma ile interrupt arasında senkronizasyon için wait queue kullanılır.
Donanım IRQ tetiklenir
│
▼
irq_handler() [interrupt ctx]
• Register'dan veri oku (readl)
• ring buffer'a yaz (spinlock)
• schedule_work()
│
▼ (deferred — process ctx)
workqueue handler
• wake_up_interruptible(&wait_q)
│
Kullanıcı tarafı (ayrı thread):
read() → wait_event_interruptible(wait_q, data_ready)
→ buffer kopyala → copy_to_user → geri döndür
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>
MODULE_LICENSE("GPL");
#define BUF_SIZE 256
struct irqdev_priv {
void __iomem *base;
int irq;
char buf[BUF_SIZE];
size_t buf_len;
bool data_ready;
spinlock_t lock;
wait_queue_head_t wait_q;
struct work_struct work;
dev_t devno;
struct cdev cdev;
struct class *cls;
};
/* Workqueue handler — process context */
static void irqdev_work(struct work_struct *work)
{
struct irqdev_priv *priv =
container_of(work, struct irqdev_priv, work);
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
priv->data_ready = true;
spin_unlock_irqrestore(&priv->lock, flags);
wake_up_interruptible(&priv->wait_q);
}
/* Interrupt handler — top half */
static irqreturn_t irqdev_handler(int irq, void *data)
{
struct irqdev_priv *priv = data;
unsigned long flags;
u32 reg_val;
reg_val = readl(priv->base + 0x04);
spin_lock_irqsave(&priv->lock, flags);
priv->buf_len = snprintf(priv->buf, BUF_SIZE,
"reg=0x%08x\n", reg_val);
spin_unlock_irqrestore(&priv->lock, flags);
schedule_work(&priv->work);
return IRQ_HANDLED;
}
/* character device read() — wait queue ile bloklar */
static ssize_t irqdev_read(struct file *filp, char __user *ubuf,
size_t len, loff_t *off)
{
struct irqdev_priv *priv = filp->private_data;
unsigned long flags;
size_t to_copy;
int ret;
/* data_ready olana kadar uyku — sinyal gelirse ERESTARTSYS */
ret = wait_event_interruptible(priv->wait_q, priv->data_ready);
if (ret)
return -ERESTARTSYS;
spin_lock_irqsave(&priv->lock, flags);
to_copy = min(len, priv->buf_len);
if (copy_to_user(ubuf, priv->buf, to_copy)) {
spin_unlock_irqrestore(&priv->lock, flags);
return -EFAULT;
}
priv->data_ready = false;
spin_unlock_irqrestore(&priv->lock, flags);
return to_copy;
}
static int irqdev_open(struct inode *inode, struct file *filp)
{
struct irqdev_priv *priv =
container_of(inode->i_cdev, struct irqdev_priv, cdev);
filp->private_data = priv;
return 0;
}
static const struct file_operations irqdev_fops = {
.owner = THIS_MODULE,
.open = irqdev_open,
.read = irqdev_read,
};
wait_event_interruptible() sadece process context'te çağrılabilir — interrupt handler veya atomic context içinde çağrılamaz. priv->data_ready condition expression'ı spinlock altında yazılmalı ve read edilmelidir; aksi hâlde race condition oluşur.
Bu bölümde
- Interrupt → workqueue → wait queue → read() veri akış zinciri
- wake_up_interruptible() + wait_event_interruptible() çifti
- container_of() — cdev pointer'ından private struct'a ulaşma
- spinlock ile interrupt handler ve read() arasında data_ready senkronizasyonu
- readl() — memory-mapped register okuma; interrupt context'te güvenli
07 Clock ve regulator yönetimi
SoC peripheral'larının büyük çoğunluğu clock ve power rail bağımlıdır; Linux clock framework ve regulator framework bu kaynakları soyutlar.
Clock framework
Peripheral register'larına erişmeden önce clock'un etkinleştirilmesi gerekir. clk_prepare_enable() iki adımı birleştirir: clk_prepare() (uyku yapılabilir hazırlık) + clk_enable() (atomik aktifleştirme).
#include <linux/clk.h>
struct clk_priv {
struct clk *apb_clk;
struct clk *ref_clk;
};
static int clkdev_probe(struct platform_device *pdev)
{
struct clk_priv *priv;
unsigned long rate;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* DTS clock-names = "apb_pclk", "ref_clk" */
priv->apb_clk = devm_clk_get(&pdev->dev, "apb_pclk");
if (IS_ERR(priv->apb_clk))
return dev_err_probe(&pdev->dev, PTR_ERR(priv->apb_clk),
"APB clock alinamadi\n");
/* _optional: DT'de yoksa NULL döner, hata vermez */
priv->ref_clk = devm_clk_get_optional(&pdev->dev, "ref_clk");
if (IS_ERR(priv->ref_clk))
return PTR_ERR(priv->ref_clk);
/* prepare + enable tek adimda */
ret = clk_prepare_enable(priv->apb_clk);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"APB clock etkinlestirilemedi\n");
rate = clk_get_rate(priv->apb_clk);
dev_info(&pdev->dev, "APB clock: %lu Hz\n", rate);
/* Clock hizini ayarla — donanim destekliyorsa */
ret = clk_set_rate(priv->apb_clk, 50000000);
if (ret)
dev_warn(&pdev->dev, "clock rate ayarlanamadi: %d\n", ret);
platform_set_drvdata(pdev, priv);
return 0;
}
static int clkdev_remove(struct platform_device *pdev)
{
struct clk_priv *priv = platform_get_drvdata(pdev);
clk_disable_unprepare(priv->apb_clk);
return 0; /* devm_clk_get → clk_put otomatik */
}
Regulator framework
#include <linux/regulator/consumer.h>
struct reg_priv { struct regulator *vdd; };
static int regdev_probe(struct platform_device *pdev)
{
struct reg_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* DTS: vdd-supply = <®_3v3>; */
priv->vdd = devm_regulator_get(&pdev->dev, "vdd");
if (IS_ERR(priv->vdd))
return dev_err_probe(&pdev->dev, PTR_ERR(priv->vdd),
"VDD regulator alinamadi\n");
ret = regulator_enable(priv->vdd);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"VDD etkinlestirilemedi\n");
dev_info(&pdev->dev, "VDD: %d mV\n",
regulator_get_voltage(priv->vdd) / 1000);
platform_set_drvdata(pdev, priv);
return 0;
}
static int regdev_remove(struct platform_device *pdev)
{
struct reg_priv *priv = platform_get_drvdata(pdev);
regulator_disable(priv->vdd);
return 0;
}
Runtime Power Management
/* probe() sonunda runtime PM etkinleştir */
pm_runtime_set_autosuspend_delay(&pdev->dev, 100); /* 100 ms */
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
/* driver operasyonu öncesi — device uyandır */
pm_runtime_get_sync(&pdev->dev);
/* operasyon bitti — otomatik uyku planla */
pm_runtime_put_autosuspend(&pdev->dev);
/* remove() içinde */
pm_runtime_disable(&pdev->dev);
Bu bölümde
- devm_clk_get() + clk_prepare_enable() — clock al ve etkinleştir
- clk_get_rate() / clk_set_rate() — clock hızı okuma ve ayarlama
- clk_disable_unprepare() — remove()'da simetrik kapatma
- devm_regulator_get() + regulator_enable/disable() — güç kaynağı yönetimi
- Runtime PM: pm_runtime_get_sync / put_autosuspend — dinamik güç tasarrufu
08 Tam driver örneği: UART benzeri cihaz
of_match_table, probe, IRQ handler, workqueue, character device ve sysfs'i tek çatı altında birleştiren production-ready platform driver örneği.
Register haritası ve DTS
| Offset | Register | Açıklama |
|---|---|---|
| 0x00 | DATA_REG | Okuma: RX FIFO'dan 1 byte; Yazma: TX FIFO'ya gönder |
| 0x04 | STATUS_REG | bit0: RX_READY, bit1: TX_EMPTY, bit2: ERROR |
| 0x08 | CTRL_REG | bit0: RX_IRQ_EN, bit1: TX_IRQ_EN, bit7: RESET |
| 0x0C | BAUD_REG | Divisor = clk_freq / (16 * baud) |
/ {
myuart0: myuart@20000000 {
compatible = "myvendor,myuart";
reg = <0x20000000 0x100>;
interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_uart>;
clock-names = "uart_clk";
clock-frequency = <48000000>;
status = "okay";
};
};
Tam driver kodu
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
MODULE_DESCRIPTION("myuart — UART benzeri platform driver");
#define DATA_REG 0x00
#define STATUS_REG 0x04
#define CTRL_REG 0x08
#define BAUD_REG 0x0C
#define STATUS_RX_READY BIT(0)
#define STATUS_TX_EMPTY BIT(1)
#define CTRL_RX_IRQ_EN BIT(0)
#define CTRL_RESET BIT(7)
#define RX_BUF_SIZE 1024
#define DEVICE_NAME "myuart"
#define CLASS_NAME "myuart_class"
struct myuart_priv {
void __iomem *base;
int irq;
struct clk *clk;
u32 clk_freq;
char rx_buf[RX_BUF_SIZE];
unsigned int rx_head;
unsigned int rx_tail;
spinlock_t rx_lock;
wait_queue_head_t rx_wait;
struct work_struct rx_work;
dev_t devno;
struct cdev cdev;
struct class *cls;
struct device *dev_node;
u32 baud;
};
static inline u32 myuart_readl(struct myuart_priv *p, u32 o) { return readl(p->base + o); }
static inline void myuart_writel(struct myuart_priv *p, u32 v, u32 o) { writel(v, p->base + o); }
static void myuart_rx_work(struct work_struct *work)
{
struct myuart_priv *priv = container_of(work, struct myuart_priv, rx_work);
wake_up_interruptible(&priv->rx_wait);
}
static irqreturn_t myuart_irq(int irq, void *data)
{
struct myuart_priv *priv = data;
unsigned long flags;
char ch;
if (!(myuart_readl(priv, STATUS_REG) & STATUS_RX_READY))
return IRQ_NONE;
spin_lock_irqsave(&priv->rx_lock, flags);
while (myuart_readl(priv, STATUS_REG) & STATUS_RX_READY) {
unsigned int next;
ch = (char)myuart_readl(priv, DATA_REG);
next = (priv->rx_head + 1) & (RX_BUF_SIZE - 1);
if (next != priv->rx_tail) {
priv->rx_buf[priv->rx_head] = ch;
priv->rx_head = next;
}
}
spin_unlock_irqrestore(&priv->rx_lock, flags);
schedule_work(&priv->rx_work);
return IRQ_HANDLED;
}
static int myuart_open(struct inode *inode, struct file *filp)
{
filp->private_data = container_of(inode->i_cdev, struct myuart_priv, cdev);
return 0;
}
static int myuart_release(struct inode *i, struct file *f) { return 0; }
static ssize_t myuart_read(struct file *filp, char __user *ubuf,
size_t len, loff_t *off)
{
struct myuart_priv *priv = filp->private_data;
unsigned long flags;
size_t copied = 0;
if (wait_event_interruptible(priv->rx_wait,
priv->rx_head != priv->rx_tail))
return -ERESTARTSYS;
spin_lock_irqsave(&priv->rx_lock, flags);
while (copied < len && priv->rx_tail != priv->rx_head) {
char ch = priv->rx_buf[priv->rx_tail];
priv->rx_tail = (priv->rx_tail + 1) & (RX_BUF_SIZE - 1);
spin_unlock_irqrestore(&priv->rx_lock, flags);
if (put_user(ch, ubuf + copied))
return copied ? (ssize_t)copied : -EFAULT;
copied++;
spin_lock_irqsave(&priv->rx_lock, flags);
}
spin_unlock_irqrestore(&priv->rx_lock, flags);
return (ssize_t)copied;
}
static ssize_t myuart_write(struct file *filp, const char __user *ubuf,
size_t len, loff_t *off)
{
struct myuart_priv *priv = filp->private_data;
size_t i;
char ch;
for (i = 0; i < len; i++) {
if (get_user(ch, ubuf + i))
return i ? (ssize_t)i : -EFAULT;
while (!(myuart_readl(priv, STATUS_REG) & STATUS_TX_EMPTY))
cpu_relax();
myuart_writel(priv, ch, DATA_REG);
}
return (ssize_t)len;
}
static const struct file_operations myuart_fops = {
.owner = THIS_MODULE,
.open = myuart_open,
.release = myuart_release,
.read = myuart_read,
.write = myuart_write,
};
static ssize_t baud_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct myuart_priv *priv = dev_get_drvdata(dev);
return sysfs_emit(buf, "%u\n", priv->baud);
}
static ssize_t baud_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct myuart_priv *priv = dev_get_drvdata(dev);
u32 baud, divisor;
if (kstrtou32(buf, 10, &baud) || !baud)
return -EINVAL;
divisor = priv->clk_freq / (16 * baud);
myuart_writel(priv, divisor, BAUD_REG);
priv->baud = baud;
dev_info(dev, "baud=%u divisor=%u\n", baud, divisor);
return count;
}
static DEVICE_ATTR_RW(baud);
static struct attribute *myuart_attrs[] = { &dev_attr_baud.attr, NULL };
static const struct attribute_group myuart_group = { .attrs = myuart_attrs };
static int myuart_probe(struct platform_device *pdev)
{
struct myuart_priv *priv;
struct resource *res;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) return -ENOMEM;
spin_lock_init(&priv->rx_lock);
init_waitqueue_head(&priv->rx_wait);
INIT_WORK(&priv->rx_work, myuart_rx_work);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->base)) return PTR_ERR(priv->base);
priv->clk = devm_clk_get(&pdev->dev, "uart_clk");
if (IS_ERR(priv->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk),
"clock alinamadi\n");
ret = clk_prepare_enable(priv->clk);
if (ret) return ret;
of_property_read_u32(pdev->dev.of_node, "clock-frequency",
&priv->clk_freq);
if (!priv->clk_freq)
priv->clk_freq = clk_get_rate(priv->clk);
/* Donanımı sıfırla, RX IRQ etkinleştir, baud rate ayarla */
myuart_writel(priv, CTRL_RESET, CTRL_REG);
myuart_writel(priv, CTRL_RX_IRQ_EN, CTRL_REG);
priv->baud = 115200;
myuart_writel(priv, priv->clk_freq / (16 * 115200), BAUD_REG);
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) { ret = priv->irq; goto err_clk; }
ret = devm_request_irq(&pdev->dev, priv->irq, myuart_irq,
0, "myuart", priv);
if (ret) goto err_clk;
ret = alloc_chrdev_region(&priv->devno, 0, 1, DEVICE_NAME);
if (ret) goto err_clk;
cdev_init(&priv->cdev, &myuart_fops);
priv->cdev.owner = THIS_MODULE;
ret = cdev_add(&priv->cdev, priv->devno, 1);
if (ret) goto err_unreg;
priv->cls = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(priv->cls)) { ret = PTR_ERR(priv->cls); goto err_cdev; }
priv->dev_node = device_create(priv->cls, &pdev->dev,
priv->devno, priv, DEVICE_NAME);
if (IS_ERR(priv->dev_node)) {
ret = PTR_ERR(priv->dev_node); goto err_class;
}
ret = sysfs_create_group(&priv->dev_node->kobj, &myuart_group);
if (ret) goto err_devnode;
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev,
"myuart probe OK — /dev/%s IRQ=%d clk=%u baud=%u\n",
DEVICE_NAME, priv->irq, priv->clk_freq, priv->baud);
return 0;
err_devnode: device_destroy(priv->cls, priv->devno);
err_class: class_destroy(priv->cls);
err_cdev: cdev_del(&priv->cdev);
err_unreg: unregister_chrdev_region(priv->devno, 1);
err_clk: clk_disable_unprepare(priv->clk);
return ret;
}
static int myuart_remove(struct platform_device *pdev)
{
struct myuart_priv *priv = platform_get_drvdata(pdev);
sysfs_remove_group(&priv->dev_node->kobj, &myuart_group);
device_destroy(priv->cls, priv->devno);
class_destroy(priv->cls);
cdev_del(&priv->cdev);
unregister_chrdev_region(priv->devno, 1);
cancel_work_sync(&priv->rx_work);
clk_disable_unprepare(priv->clk);
/* devm_: ioremap, irq, kzalloc otomatik temizlenir */
dev_info(&pdev->dev, "myuart kaldirildi\n");
return 0;
}
static const struct of_device_id myuart_of_match[] = {
{ .compatible = "myvendor,myuart" },
{ }
};
MODULE_DEVICE_TABLE(of, myuart_of_match);
static struct platform_driver myuart_driver = {
.probe = myuart_probe,
.remove = myuart_remove,
.driver = {
.name = "myuart",
.of_match_table = myuart_of_match,
},
};
module_platform_driver(myuart_driver);
Makefile ve derleme
obj-m := myuart.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
Test adımları
# 1. Derle
$ make
CC [M] myuart.o
LD [M] myuart.ko
# 2. Modülü yükle
$ sudo insmod myuart.ko
[ 10.001] myuart: probe OK — /dev/myuart IRQ=55 clk=48000000 baud=115200
# 3. Baud rate'i sysfs üzerinden değiştir
$ echo 9600 | sudo tee /sys/class/myuart_class/myuart/baud
$ cat /sys/class/myuart_class/myuart/baud
9600
# 4. Veri gönder
$ echo "AT+TEST" | sudo tee /dev/myuart
# 5. Veri oku (interrupt tabanlı — veri gelince döner)
$ sudo cat /dev/myuart
OK
# 6. Modülü kaldır
$ sudo rmmod myuart
[ 45.112] myuart: myuart kaldirildi
Bu örnekte character device için devm_ kullanılamamıştır çünkü class_create() ve device_create()'in devm_ versiyonları (devm_class_create()) kernel 6.4'te eklenmiştir. Daha eski kernel'larda goto cleanup pattern veya özel devm_add_action_or_reset() wrapper kullanılmalıdır.
Bu bölümde
- Tam myuart driver: DTS → probe → IRQ + workqueue + wait queue + chardev + sysfs
- Ring buffer ile RX FIFO yönetimi; spinlock + wait_event_interruptible entegrasyonu
- baud rate sysfs attribute: kstrtou32 + register yazma — çalışırken değiştirilebilir
- probe() goto cleanup pattern: devm_ ile yönetilemeyen chardev kaynakları için
- remove() ters sıra: sysfs → device → class → cdev → cancel_work → clk