장치 트리 환경에서 platform 드라이버 개발
platform 드라이버 아키텍처는 버스, 장치, 드라이버로 구성되며, 버스는 Linux 커널이 관리하므로 사용자가 직접 처리할 필요가 없습니다. 장치 트리를 사용할 경우, 단지 platform_driver 구조체만 구현하면 됩니다.
1. pinctrl-stm32.c 파일 수정
pinctrl 서브시스템은 핀의 설정 정보와 전기적 특성(복용, 풀업/풀다운, 속도 등)을 장치 트리에서 정의하고, gpio 서브시스템은 해당 핀을 입력/출력 모드로 설정하거나 값을 읽는 등의 작업을 수행합니다.
STM32MP1 플랫폼에서는 pinctrl 설정이 반드시 platform 계층에서 참조되어야 하며, 사용하는 칩에 따라 달라집니다. 특정 핀을 GPIO로 사용하기 위해선 먼저 pinctrl-stm32.c 파일을 수정해야 합니다.
다음 경로로 이동하여 파일을 열고 수정합니다:
/linux/atk-mpl/linux/my_linux/linux-5.4.31/drivers/pinctrl/stm32/pinctrl-stm32.c
865행부터 시작되는 stm32_pmx_ops 구조체에서 다음 변경을 수행합니다:
static const struct pinmux_ops stm32_pmx_ops = {
.get_functions_count = stm32_pmx_get_funcs_cnt,
.get_function_name = stm32_pmx_get_func_name,
.get_function_groups = stm32_pmx_get_func_groups,
.set_mux = stm32_pmx_set_mux,
.gpio_set_direction = stm32_pmx_gpio_set_direction,
.strict = false, // 원래 값은 true였으나, 비엄격 모드로 변경
};
수정 후 커널 재컴파일:
make uImage LOADADDR=0XC2000040 -j16
uImage는 생성된 커널 이미지 형식이며, LOADADDR는 커널이 로딩될 주소입니다.
생성된 이미지를 TFTP 폴더로 복사:
cd arch/arm/boot/
sudo cp uImage /home/alientek/linux/tftpboot/ -f
2. 장치의 pinctrl 노드 생성
stm32mp15-pinctrl.dtsi 파일을 열어 모든 핀 설정을 포함한 pinctrl 섹션에 새로운 핀 설정을 추가합니다.
3. 장치 트리에 장치 노드 추가
stm32mp157d-atk.dts 파일을 열어 compatible 속성 값을 적절히 설정해야 합니다. platform 버스는 이 속성을 통해 드라이버와 장치를 매칭합니다.
4. platform 드라이버의 호환성 속성 설정
장치 트리를 사용할 경우, of_match_table를 통해 드라이버가 어떤 장치와 호환되는지 지정합니다. 예시 코드:
static const struct of_device_id led_of_match[] = {
{ .compatible = "alientek,led" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_platform_driver = {
.driver = {
.name = "stm32mp1-led",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
핀 복용 설정 검증
1. pinctrl 설정 확인
임베디드 리눅스 시스템에서는 한 핀은 하나의 기능만 할당해야 합니다. 예를 들어, PI0 핀이 LED 제어용 GPIO로 사용된다면, 다른 기능으로 복용되어서는 안 됩니다. ST 공식 설계에서는 PI0가 LCD_G5로 설정되어 있으므로, 이를 비활성화해야 합니다. 또한, ANALOG_INPUT 모드로 설정된 경우도 동일하게 처리해야 합니다.
2. GPIO 사용 중복 여부 점검
특정 GPIO를 사용할 때는 장치 트리 내에서 다른 장치가 그 GPIO를 사용하지 않는지 반드시 확인해야 합니다. 유일한 장치만 해당 GPIO를 사용하도록 해야 합니다.
실험용 프로그램 작성
먼저 장치 트리 파일을 수정하여 LED0 관련 정보를 추가합니다. LED 핀의 pinctrl 노드와 장치 노드를 생성하고, 아래 명령어로 DTB 파일을 다시 컴파일합니다:
cd linux/atk-mpl/linux/my_linux/linux-5.4.31
make dtbs -j32
sudo cp stm32mp157d-atk.dtb /home/alientek/linux/tftpboot/ -f
1. platform 드라이버 구현
Drivers/18_dtsplatform/leddriver.c 파일에 다음 내용을 작성합니다:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT 1
#define LEDDEV_NAME "dtsplatled"
#define LED_OFF 0
#define LED_ON 1
struct led_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *node;
int gpio_pin;
};
static struct led_dev led_data;
void led_control(int state)
{
if (state == LED_ON)
gpio_set_value(led_data.gpio_pin, 0);
else if (state == LED_OFF)
gpio_set_value(led_data.gpio_pin, 1);
}
static int led_gpio_setup(struct device_node *np)
{
int ret;
led_data.gpio_pin = of_get_named_gpio(np, "led-gpio", 0);
if (!gpio_is_valid(led_data.gpio_pin)) {
printk(KERN_ERR "Failed to get GPIO\n");
return -EINVAL;
}
ret = gpio_request(led_data.gpio_pin, "LED_PIN");
if (ret) {
printk(KERN_ERR "GPIO request failed\n");
return ret;
}
gpio_direction_output(led_data.gpio_pin, 1);
return 0;
}
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offp)
{
unsigned char data;
int ret;
ret = copy_from_user(&data, buf, cnt);
if (ret < 0) {
printk("Write error\n");
return -EFAULT;
}
if (data == LED_ON)
led_control(LED_ON);
else if (data == LED_OFF)
led_control(LED_OFF);
return cnt;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *pdev)
{
int ret;
printk("Device matched with driver!\n");
ret = led_gpio_setup(pdev->dev.of_node);
if (ret < 0)
return ret;
ret = alloc_chrdev_region(&led_data.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
if (ret < 0) {
pr_err("Failed to allocate device number\n");
goto free_gpio;
}
led_data.cdev.owner = THIS_MODULE;
cdev_init(&led_data.cdev, &led_fops);
ret = cdev_add(&led_data.cdev, led_data.devid, LEDDEV_CNT);
if (ret < 0)
goto del_unregister;
led_data.class = class_create(THIS_MODULE, LEDDEV_NAME);
if (IS_ERR(led_data.class))
goto del_cdev;
led_data.device = device_create(led_data.class, NULL, led_data.devid, NULL, LEDDEV_NAME);
if (IS_ERR(led_data.device))
goto destroy_class;
return 0;
destroy_class:
class_destroy(led_data.class);
del_cdev:
cdev_del(&led_data.cdev);
del_unregister:
unregister_chrdev_region(led_data.devid, LEDDEV_CNT);
free_gpio:
gpio_free(led_data.gpio_pin);
return -EIO;
}
static int led_remove(struct platform_device *pdev)
{
gpio_set_value(led_data.gpio_pin, 1);
gpio_free(led_data.gpio_pin);
cdev_del(&led_data.cdev);
unregister_chrdev_region(led_data.devid, LEDDEV_CNT);
device_destroy(led_data.class, led_data.devid);
class_destroy(led_data.class);
return 0;
}
static const struct of_device_id led_match[] = {
{ .compatible = "alientek,led" },
{ /* End marker */ }
};
MODULE_DEVICE_TABLE(of, led_match);
static struct platform_driver led_driver = {
.driver = {
.name = "stm32mp1-led",
.of_match_table = led_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_INFO(intree, "Y");
2. 테스트 애플리케이션 작성
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define LED_OFF 0
#define LED_ON 1
int main(int argc, char *argv[])
{
int fd;
unsigned char cmd;
char *file_path;
if (argc != 3) {
printf("Usage: %s <device_file> <0 or 1>\n", argv[0]);
return -1;
}
file_path = argv[1];
cmd = atoi(argv[2]);
fd = open(file_path, O_RDWR);
if (fd < 0) {
printf("Cannot open device\n");
return -1;
}
write(fd, &cmd, sizeof(cmd));
close(fd);
return 0;
}
테스트 실행
Makefile 파일 작성:
KERNELDIR := /home/alientek/linux/atk-mpl/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
빌드 및 실행:
make -j32
arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp
sudo cp ledApp leddriver.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f
개발보드에서 다음 명령어 실행:
cd lib/modules/5.4.31/
depmod
modprobe leddriver.ko
드라이버 로딩 후, /sys/bus/platform/drivers/ 디렉터리에 stm32mp1-led 항목이 생성됩니다.
LED 제어 테스트:
./ledApp /dev/dtsplatled 1 // LED ON
./ledApp /dev/dtsplatled 0 // LED OFF
드라이버 제거:
rmmod leddriver.ko
요약
장치 트리 기반의 platform 드라이버 개발은 다음과 같은 단계로 진행됩니다:
pinctrl-stm32.c파일에서 엄격 모드를 비활성화해 핀 재사용을 허용- 핀 설정 정보를
stm32mp15-pinctrl.dtsi에 추가 - 장치 트리(
.dts) 파일에 장치 노드 및pinctrl-names,pinctrl-0설정 - 핀 복용 및 GPIO 중복 사용 여부 점검
probe함수에서 장치 초기화,remove함수에서 자원 해제of_match_table를 통해 장치 트리의compatible속성과 매칭
이 과정을 통해 장치 트리 기반의 신뢰성 있는 platform 드라이버가 완성됩니다.