<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 驅動的需求。
:::

參考[總線驅動程式碼邏輯](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`查看設備樹

:::
# 應用層代碼
請參考[【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`

> **為何會出現`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`

gpioled的名稱 是跟[device tree](#設備樹代碼) 中`gpioled`節點的名稱一樣
:::
## Q 總線驅動中跟device跟driver是如何匹配的?
> 主要透過 總線驅動程式 中的 `.compatible` 與 設備樹 中的 `compatible` 的內容進行匹配,兩部分的 `compatible` <font color="#f00">字段內容需要完全一致,才能實現設備與驅動程式的成功匹配</font>。
|  | 
| -------- | -------- |
| 總線驅動 | 設備樹 |
# 實驗結果
開燈
```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)