4. GPIO驱动开发与LED控制
任务目标
在TI Jacinto平台(以J721E为例)上实现一个基础GPIO字符设备驱动,将特定引脚配置为输出模式并连接板载LED,通过应用层控制LED以1Hz频率闪烁,以此验证GPIO寄存器的读写操作与驱动框架的完整工作链路。
硬件连接与引脚定义
本案例以J721E EVM板载用户LED(通常连接至WKUP_GPIO0_12,对应控制器的第12号引脚)为目标对象。若实际板卡引脚不同,需根据原理图修改设备树中的gpios属性。驱动通过memory-mapped I/O直接操作GPIO控制器寄存器,不依赖现有GPIO子系统,以展示底层寄存器读写过程。
核心实现
驱动实现分为三部分:寄存器定义与操作函数、文件操作接口、平台驱动注册。LED闪烁通过定时器在驱动内部完成,应用层只需打开设备节点即可触发,无需反复写入。关键点在于GPIO输出方向寄存器的设置与数据寄存器的位操作。
// gpio_led_drv.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#define GPIO_DIR_REG 0x0
#define GPIO_DATA_REG 0x8
#define GPIO_SETDATA_REG 0x10
#define GPIO_CLEARDATA_REG 0x18
struct gpio_led_dev {
void __iomem *base;
int gpio_num;
struct timer_list timer;
int led_state;
};
static struct gpio_led_dev *led_dev;
static void led_timer_cb(struct timer_list *t) {
struct gpio_led_dev *dev = from_timer(dev, t, timer);
u32 val;
dev->led_state = !dev->led_state;
if (dev->led_state)
writel(BIT(dev->gpio_num), dev->base + GPIO_SETDATA_REG);
else
writel(BIT(dev->gpio_num), dev->base + GPIO_CLEARDATA_REG);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500));
}
static ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
char state = led_dev->led_state ? '1' : '0';
if (copy_to_user(buf, &state, 1))
return -EFAULT;
return 1;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
char kbuf[4];
int on;
if (count > 3) count = 3;
if (copy_from_user(kbuf, buf, count))
return -EFAULT;
kbuf[count] = '\0';
if (kstrtoint(kbuf, 10, &on) == 0) {
if (on == 1) {
led_dev->led_state = 1;
writel(BIT(led_dev->gpio_num), led_dev->base + GPIO_SETDATA_REG);
} else if (on == 0) {
led_dev->led_state = 0;
writel(BIT(led_dev->gpio_num), led_dev->base + GPIO_CLEARDATA_REG);
}
}
return count;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
};
static struct miscdevice led_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "gpio_led",
.fops = &led_fops,
};
static int gpio_led_probe(struct platform_device *pdev) {
struct device *dev = &pdev->dev;
struct resource *res;
int ret;
led_dev = devm_kzalloc(dev, sizeof(*led_dev), GFP_KERNEL);
if (!led_dev)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
led_dev->base = devm_ioremap_resource(dev, res);
if (IS_ERR(led_dev->base))
return PTR_ERR(led_dev->base);
ret = of_property_read_u32(dev->of_node, "gpio-offset", &led_dev->gpio_num);
if (ret < 0) {
dev_err(dev, "missing gpio-offset property\n");
return -EINVAL;
}
// 配置为输出模式:方向寄存器对应位写1
writel(BIT(led_dev->gpio_num), led_dev->base + GPIO_DIR_REG);
// 初始关闭LED
writel(BIT(led_dev->gpio_num), led_dev->base + GPIO_CLEARDATA_REG);
led_dev->led_state = 0;
timer_setup(&led_dev->timer, led_timer_cb, 0);
mod_timer(&led_dev->timer, jiffies + msecs_to_jiffies(500));
ret = misc_register(&led_miscdev);
if (ret) {
dev_err(dev, "misc_register failed\n");
return ret;
}
dev_info(dev, "GPIO LED driver probed, gpio=%d\n", led_dev->gpio_num);
return 0;
}
static int gpio_led_remove(struct platform_device *pdev) {
del_timer_sync(&led_dev->timer);
misc_deregister(&led_miscdev);
return 0;
}
static const struct of_device_id gpio_led_of_match[] = {
{ .compatible = "ti,j721e-gpio-led-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, gpio_led_of_match);
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.remove = gpio_led_remove,
.driver = {
.name = "gpio_led_demo",
.of_match_table = gpio_led_of_match,
},
};
module_platform_driver(gpio_led_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TI Jacinto GPIO LED control demo");
设备树节点配置
在J721E EVM的设备树文件中,需在根节点下添加以下节点,指定GPIO控制器基地址与引脚偏移。地址值0x02940000对应WKUP_GPIO0控制器,具体需查阅TRM确认。
/ {
gpio_led_demo {
compatible = "ti,j721e-gpio-led-demo";
reg = <0x00 0x02940000 0x00 0x100>; // WKUP_GPIO0 寄存器基地址
gpio-offset = <12>; // 对应WKUP_GPIO0_12
};
};
编译与加载
- 将驱动源码放入内核drivers/misc/目录,并在Kconfig/Makefile中添加对应条目,或使用外部模块编译方式:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=$PWD modules
- 将生成的
gpio_led_drv.ko拷贝至目标板,加载并验证:
insmod gpio_led_drv.ko
ls /dev/gpio_led
验证方法
加载驱动后观察板载LED是否以1Hz频率闪烁(亮500ms灭500ms)。若LED未闪烁,执行以下检查:
dmesg | tail查看驱动probe是否成功,确认打印的gpio编号与硬件一致;- 使用示波器或万用表测量对应引脚电平是否在0V与3.3V间切换;
- 向设备节点写入控制状态:
echo 0 > /dev/gpio_led应使LED常灭,echo 1 > /dev/gpio_led应使LED常亮,读取cat /dev/gpio_led返回当前状态字符。
通过标准:LED按预期频率闪烁,且echo/cat操作能正确控制与读取状态,说明GPIO寄存器读写与驱动框架工作正常。