---
# System prepended metadata

title: 【embeded_linux】6.LED 字符驅動基於總線通信，並透過設備樹描述的 pinctrl 配置進行引腳管理
tags: [Embedded_linux]

---

<style>
table {
  word-break: normal !important;
}
</style>
# 【embeded_linux】6.led字符驅動基於總線、設備樹裡使用pinctrl

:::info
 **實驗目標**: 實際驅動總線的應用，搭配設備樹加pinctrl
 **實驗目的**:控制開發版上的led燈
 **實驗器材**:正點原子STM32MP157 Mini開發板、筆電、 USB Type-C 轉 RJ45 Gigabit網路卡
:::
# 總線使用設備樹的好處
1. **沒有使用設備樹的內核**：  
   - 開發者需要分別撰寫 **platform_device**（描述設備信息）和 **platform_driver**（實現驅動邏輯）。  
2. **使用設備樹的內核**：  
   - 設備信息被移到 **設備樹（Device Tree）** 中，開發者只需實現 **platform_driver**，<font color="#f00">不再需要向[上一篇](https://hackmd.io/@a0979552111/B1QOHXOQkl)手動編寫 **platform_device**。</font>
:::success
設備樹統一了設備的描述，透過**設備樹**的配置，可以減少撰寫額外 device 驅動的需求。  
:::
![image](https://hackmd.io/_uploads/SJXJfe-PJg.png)


參考[總線驅動程式碼邏輯](https://hackmd.io/@a0979552111/HkuFF9wIJx)


# 應用層代碼
請參考[【embeded_linux】新字符設備驅動程式撰寫](https://hackmd.io/@a0979552111/Sym0Ebv-ye)
應用程代碼 複製下來即可
# 設備樹代碼
修改跟上一節(代寫)
```css=
gpioled {
		compatible = "alientek,led";
		pinctrl-names = "default";
		status = "okay";
		pinctrl-0 = <&led_pins_a>;
		led-gpio = <&gpioi  0  GPIO_ACTIVE_LOW>;
};
```
設備樹代碼完整版
:::spoiler 打開設備樹代碼
```cpp=
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * Copyright (C) STMicroelectronics 2019 - All Rights Reserved
 * Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics.
 */
/dts-v1/;

#include "stm32mp157.dtsi"
#include "stm32mp15xd.dtsi"
#include "stm32mp15-pinctrl.dtsi"
#include "stm32mp15xxaa-pinctrl.dtsi"
#include "stm32mp157-m4-srm.dtsi"
#include "stm32mp157-m4-srm-pinctrl.dtsi"
#include "stm32mp157d-atk.dtsi"
#include "dt-bindings/input/input.h"
#include <dt-bindings/usb/pd.h>

/ {
    model = "STMicroelectronics STM32MP157D eval daughter";
    compatible = "st,stm32mp157d-ed1", "st,stm32mp157";

    chosen {
        stdout-path = "serial0:115200n8";
    };

    aliases {
        serial0 = &uart4;
        serial1 = &uart5;
        serial2 = &usart3;
    };
    
	reserved-memory {
		gpu_reserved: gpu@f6000000 {
			reg = <0xf6000000 0x8000000>;
			no-map;
		};

		optee_memory: optee@fe000000 {
			reg = <0xfe000000 0x02000000>;
			no-map;
		};
	};


	gpioled {
		compatible = "alientek,led";
		pinctrl-names = "default";
		status = "okay";
		pinctrl-0 = <&led_pins_a>;
		led-gpio = <&gpioi  0  GPIO_ACTIVE_LOW>;
	};
}
```
:::

#### makefile
```makefile!
KERNELDIR := /home/alientek/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)
obj-m := leddriver.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean


```

:::success
修改過後，從新編譯設備樹
```shell
make dtbs
```
複製編譯過後的.dtb複製到tftp目錄底下

```shell
cp ./arch/arm/boot/dts/stm32mp157d-atk.dtb  ~/linux/tftpboot -f
```
重啟開發版，進入 /proc/device-tree/，`ls`查看設備樹
![gpioled](https://hackmd.io/_uploads/SJVqstvIkl.png)

:::
# 應用層代碼
請參考[【embeded_linux】新字符設備驅動程式撰寫](https://hackmd.io/@a0979552111/Sym0Ebv-ye)
應用程代碼 複製下來即可

# 驅動代碼
:::spoiler 打開驅動程式碼
```cpp=
//leddriver.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>

#define LEDDEV_CNT 1                /* 設備號長度 */
#define LEDDEV_NAME "dtsplatled"    /* 設備名字 */
#define LEDON 0                     /* 開 LED */
#define LEDOFF 1                    /* 關 LED */

struct leddev_dev {
    dev_t devid;                    /* 設備號 */
    struct cdev cdev;               /* cdev */
    struct class *class;            /* 類 */
    struct device *device;          /* 設備 */
    int gpio_led;                   /* GPIO 編號 */
};

static struct leddev_dev leddev;

/* 控制 LED 狀態 */
static void led_switch(u8 state)
{
    gpio_set_value(leddev.gpio_led, state == LEDON ? 0 : 1);
}

/* 初始化 LED GPIO */
static int led_gpio_init(struct device_node *node)
{
    int ret;

    /* 獲取 GPIO 編號 */
    leddev.gpio_led = of_get_named_gpio(node, "led-gpio", 0);
    if (!gpio_is_valid(leddev.gpio_led)) {
        pr_err("Invalid GPIO\n");
        return -EINVAL;
    }

    /* 請求 GPIO */
    ret = gpio_request(leddev.gpio_led, "LED0");
    if (ret) {
        pr_err("Failed to request GPIO\n");
        return ret;
    }

    /* 設置 GPIO 為輸出並默認為關閉 */
    gpio_direction_output(leddev.gpio_led, 1);
    return 0;
}

/* 打開設備 */
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev;
    return 0;
}

/* 寫設備 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
    unsigned char databuf[1];
    int ret;

    /* 從用戶空間讀取數據 */
    ret = copy_from_user(databuf, buf, count);
    if (ret) {
        pr_err("Failed to copy data from user\n");
        return -EFAULT;
    }

    /* 根據數據控制 LED */
    if (databuf[0] == LEDON) {
        led_switch(LEDON);
    } else if (databuf[0] == LEDOFF) {
        led_switch(LEDOFF);
    } else {
        pr_err("Invalid value\n");
        return -EINVAL;
    }

    return count;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

/* probe 函數 */
static int led_probe(struct platform_device *pdev)
{
    int ret;

    /* 初始化 LED GPIO */
    ret = led_gpio_init(pdev->dev.of_node);
    if (ret)
        return ret;

    /* 分配字符設備號 */
    ret = alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
    if (ret) {
        pr_err("Failed to allocate device number\n");
        goto err_gpio;
    }

    /* 初始化 cdev */
    cdev_init(&leddev.cdev, &led_fops);
    ret = cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
    if (ret) {
        pr_err("Failed to add cdev\n");
        goto err_unregister;
    }

    /* 創建類和設備 */
    leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
    if (IS_ERR(leddev.class)) {
        pr_err("Failed to create class\n");
        ret = PTR_ERR(leddev.class);
        goto err_cdev;
    }

    leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
    if (IS_ERR(leddev.device)) {
        pr_err("Failed to create device\n");
        ret = PTR_ERR(leddev.device);
        goto err_class;
    }

    return 0;

err_class:
    class_destroy(leddev.class);
err_cdev:
    cdev_del(&leddev.cdev);
err_unregister:
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
err_gpio:
    gpio_free(leddev.gpio_led);
    return ret;
}

/* remove 函數 */
static int led_remove(struct platform_device *pdev)
{
    gpio_set_value(leddev.gpio_led, 1); /* 關閉 LED */
    gpio_free(leddev.gpio_led);
    device_destroy(leddev.class, leddev.devid);
    class_destroy(leddev.class);
    cdev_del(&leddev.cdev);
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    return 0;
}

/* 匹配表 */
static const struct of_device_id led_of_match[] = {
    { .compatible = "alientek,led" },
    { /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, led_of_match);

/* platform 驅動 */
static struct platform_driver led_driver = {
    .driver = {
        .name = "stm32mp1-led",
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};

/* 驅動加載 */
static int __init led_init(void)
{
    return platform_driver_register(&led_driver);
}

/* 驅動卸載 */
static void __exit led_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_DESCRIPTION("A simple GPIO LED driver");

```
:::


# 實驗部分

#### makefile
```makefile!
KERNELDIR := /home/alientek/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)
obj-m := leddriver.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean


```
```shell 
#建置
$ make 
#編譯ledApp 
$ arm-none-linux-gnueabihf-gcc ledApp.c -o ledAPP
#複製gpioled.ko   ledApp 到指定資料夾
$ cp -f ./leddriver.ko   ./ledAPP  /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ 
```
- 加載模組
```shell
確保在/lib/modules/5.4.31 目錄下
depmod
modprobe leddriver.ko
```
:::success


載入模組後可以 查看`/sys/bus/platform/drivers`底下沒有出現`stm32mp1-led`
![image](https://hackmd.io/_uploads/B1cxGjvI1e.png)
> **為何會出現`stm32mp1-led`**?
原因是驅動中platform_driver結構體中.driver裡的.name 名稱所產生的
```cpp
/* platform 驅動 */
static struct platform_driver led_driver = {
    .driver = {
        .name = "stm32mp1-led",
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};
```
查看`/sys/bus/platform/device`底下沒有出現`gpioled`

![image](https://hackmd.io/_uploads/SkanMov8ye.png)
gpioled的名稱 是跟[device tree](#設備樹代碼) 中`gpioled`節點的名稱一樣
:::
## Q 總線驅動中跟device跟driver是如何匹配的?
> 主要透過 總線驅動程式 中的 `.compatible` 與 設備樹 中的 `compatible` 的內容進行匹配，兩部分的 `compatible` <font color="#f00">字段內容需要完全一致，才能實現設備與驅動程式的成功匹配</font>。

| ![image](https://hackmd.io/_uploads/ryE1IgZwJe.png) | ![image](https://hackmd.io/_uploads/Byrm_lZwke.png)
| -------- | -------- |
| 總線驅動    | 設備樹     | 



# 實驗結果
開燈
```shell
./ledAPP /dev/dtsplatled 1

```
[實驗影片連結](https://drive.google.com/file/d/1GbeAdG2vNw55v_SMlwpZtviU-QhRMr9d/view?usp=drive_link)

關燈
```shell
./ledAPP /dev/dtsplatled 0
```
[實驗影片連結](https://drive.google.com/file/d/1s87TmHgOYMuIVtjHyZkkDllHPsIkA1wN/view?usp=drive_link)



