<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)