虽然GPIO控制LED 是最简单的linux驱动,但是是初学者入门必须跨过的门槛,里面很多基础知识点,有GPIO的控制原理,字符设备驱动,设备树,gpio和pinctrl子系统,内核模块原理等等,这些知识点非常重要,都是linux驱动入门的基础。
下面我们就可以一步步来写一个GPIO控制LED的驱动。
在RK3568平台上从零开始编写LED驱动,可以按照以下步骤进行:
1.原理图
led灯利用I2C3_SDA_MO引脚即GPIO1_A0
LED灯正极接GPIO1_A0,GPIO1_A0高电平点亮LED灯,低电平熄灭LED灯。
2.编写驱动代码:
- 创建一个新的驱动文件在kernel/drivers/char/下面,
led_gpio.c
。
#include <linux/init.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>
char kbuf[1] ;
dev_t devno;
int major = 0;
int led_gpio;
int gpio_led_open(struct inode *inode, struct file *file)/* 对应的是应用程序的open*/
{
printk("gpio led open !\\n");
return 0;
}
int gpio_led_release(struct inode *inode, struct file *file) /* 对应的是应用程序的close*/
{
printk("gpio led close !\\n");
return 0;
}
ssize_t gpio_led_write(struct file *file, const char __user *buf,size_t size,loff_t *pos)
{
int ret = 0;
if(size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf,buf,size);
gpio_set_value(led_gpio,kbuf[0]);
printk("[led-gpio] write %d !\\n",kbuf[0]);
return size;
}
ssize_t gpio_led_read(struct file *file, char __user *buf,size_t size,loff_t *pos)
{
int ret = 0;
if(size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(buf,kbuf,size);
printk("[led-gpio] read %d !\\n",kbuf);
return size;
}
int gpio_led_init(void)
{
return 0;
}
int gpio_led_on(int ledno)
{
return 0;
}
int gpio_led_off(int ledno)
{
return 0;
}
#define LED_OFF 0
#define LED_ON 1
long gpio_led_ioctl(struct file *file, unsigned int cmd, unsigned long ledno)
{
printk("cmd = %d data = %ld\\n", cmd, ledno);
switch(cmd){
case LED_OFF:
gpio_led_off(ledno);
break;
case LED_ON:
gpio_led_on(ledno);
break;
default:
printk("Invalid cmd\\n");
break;
}
return ledno;
}
struct cdev led_cdev;
struct file_operations fops = {
.open = gpio_led_open,
.release = gpio_led_release,
.unlocked_ioctl = gpio_led_ioctl,
.write = gpio_led_write,
.read = gpio_led_read,
};
struct class * pcls;
struct device * pled_dev;
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_led_probe(struct platform_device *pdev) /*match成功会调用 到*/
{
int i = 0;
int ret;
printk("gpio led driver probe\\n");
/*1- 字符设备初始化*/
major = register_chrdev(0, "gpio-led", &fops);//cat /proc/devices
devno = MKDEV(major, 0);
printk("[led-gpio]major from register chrdev:%d\\n", major);
/*2- 自动创建设备节点 /dev/gpio-led-auto*/
pcls = class_create(THIS_MODULE, "led-cls");
pled_dev = device_create(pcls, NULL, devno, NULL, "gpio-led-platform");
/*3 -0 led硬件初始化*/
gpio_led_init();
test_device_node = of_find_node_by_path("/led-gpio");
if (test_device_node == NULL)
{
printk("[led-gpio]of_find_node_by_path is error \\n");
return -1;
}
led_gpio = of_get_named_gpio(test_device_node, "gpios", 0);
if (led_gpio < 0)
{
printk("[led-gpio]of_get_named_gpio is error \\n");
return -1;
}
printk("[led_gpio] is %d \\n", led_gpio);
ret = gpio_request(led_gpio, "led-gpio");
if (ret < 0)
{
printk("[led-gpio]gpio_request is error \\n");
return -1;
}
ret = gpio_direction_output(led_gpio, 1);
if(ret < 0){
printk("[led-gpio] output error\\n");
return -1;
}
gpio_set_value(led_gpio,1);
return 0;
}
int gpio_led_remove(struct platform_device *pdev)/*unmatch 时会调用*/
{
printk("[led-gpio]gpio led driver remove\\n");
unregister_chrdev(major, "gpio-led");
device_destroy(pcls, devno);
class_destroy(pcls);
return 0;
}
int gpio_led_suspend(struct platform_device *pdev, pm_message_t state)
{
printk("[led-gpio]gpio led driver suspend\\n");
return 0;
}
int gpio_led_resume(struct platform_device *pdev)
{
printk("[led-gpio]gpio led driver resume\\n");
return 0;
}
struct of_device_id led_tbl[]={
{.compatible = "led-gpio",},
};
struct platform_driver pdrv= {
.probe = gpio_led_probe,
.remove = gpio_led_remove,
.suspend = gpio_led_suspend,
.resume = gpio_led_resume,
.driver.name = "gpio-led",
.driver.of_match_table = led_tbl,
};
static __init int gpio_led_driver_init(void)
{
printk(" platform driver module init!\\n");
platform_driver_register(&pdrv);
return 0;
}
static __exit void gpio_led_driver_exit(void)
{
printk(" platform driver module exit!\\n");
platform_driver_unregister(&pdrv);
}
MODULE_LICENSE("GPL");
module_init(gpio_led_driver_init);
module_exit(gpio_led_driver_exit);
修改kernel/drivers/char/Makefile,加入下面一句
obj-y += led-gpio.o
3.设备树配置:
- 在设备树文件中添加LED设备节点。
kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi
/ {
model = "Rockchip RK3568 EVB1 DDR4 V10 Board";
compatible = "rockchip,rk3568-evb1-ddr4-v10", "rockchip,rk3568";
**led_gpio:led-gpio {
compatible = "led-gpio";
gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&led_gpio_ctrl>;
};**
&pinctrl {
**led-gpio {
led_gpio_ctrl: led-gpio-ctrl {
rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};**
4.编译驱动:
./build.sh kernel
5.烧写镜像
6.编写APP测试
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd;
char status;
if (argc != 3)
{
printf("Usage: %s <dev> <on | off>\\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\\n", argv[1]);
return -1;
}
if (0 == strcmp(argv[2], "on"))
{
status = 1;
write(fd, &status, 1);
}
else
{
status = 0;
write(fd, &status, 1);
}
close(fd);
return 0;
}
编译APP
aarch64-linux-gnu-gcc ledtest.c -o ledtest