bash
**首先介绍一下注册一个驱动的步骤:**
1、定义一个platform_driver结构
2、初始化这个结构,指定其probe、remove等函数,
并初始化其中的driver变量
3、实现其probe、remove等函数
在xxxxx.dts文件里面添加:
这里的"my_led_ctrl"
很重要要和驱动的那边的相对应。
my_led {
compatible = "my_led_ctrl";
led_ctr0 = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>;
//led_ctr1 = <&gpio1 RK_PB6 GPIO_ACTIVE_LOW>;
//led_ctr2 = <&gpio1 RK_PB7 GPIO_ACTIVE_LOW>;
status = "okay";
};
然后编写一个led控制的.c
文件,一般不会自己啦,复制一份别的在做修改,我命名为my_gpio.c
:
my_gpio.c
的内容:
/* SPDX-License-Identifier: GPL-2.0 */
#include <dt-bindings/gpio/gpio.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <linux/slab.h>
static int My_led_ID;
static ssize_t gpio_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long on = simple_strtoul(buf, NULL, 10);
if(!strcmp(attr->attr.name, "My_led_value")){
if(on){
gpio_direction_output(My_led_ID, 1);
printk("gpio_direction_output is %d \n",My_led_ID);
}
else{
gpio_direction_output(My_led_ID, 0);
printk("gpio_direction_output is 0\n");
}
}
return count;
}
static ssize_t gpio_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int tmp = 0;
if(!strcmp(attr->attr.name, "My_led_value")){
tmp = gpio_get_value(My_led_ID);
printk("gpio_get_value is %d \n",tmp);
if(tmp>0)
return strlcpy(buf, "1\n", 3);
else
return strlcpy(buf, "0\n", 3);
}
return 0;
}
/*
DEVICE_ATTR的使用 使用DEVICE_ATTR,可以在sys fs中添加“文件”,
通过修改该文件内容,可以实现在运行过程中动态控制device的目的。
类似的还有DRIVER_ATTR,BUS_ATTR,CLASS_ATTR。这几个东东的区别
就是,DEVICE_ATTR对应的文件在/sys/devices/目录中对应的device下
面。而其他几个分别在driver,bus,class中对应的目录下。这次主要
介绍DEVICE_ATTR,其他几个类似。在documentation/driver-model/Device.txt
中有对DEVICE_ATTR的详细介绍,这儿主要说明使用方法。
先看看DEVICE_ATTR的原型:
DEVICE_ATTR(_name, _mode, _show, _store) _name: 名称,也就是将在sysfs中生成的文件名称。
_mode:上述文件的访问权限,与普通文件相同,UGO的格式。
_show:显示函数,cat该文件时,此函数被调用。
_store:写函数,echo内容到该文件时,此函数被调用。
*/
// S_IRWXU | S_IRWXG 读写权限
static DEVICE_ATTR(My_led_value, S_IRWXU | S_IRWXG, gpio_show,gpio_store);
/*匹配dts文件中的compatible = "my_led_ctrl" */
static struct of_device_id My_led_of_match[] = {
{ .compatible = "my_led_ctrl" },
{ }
};
/*
MODULE_DEVICE_TABLE一般用在热插拔的设备驱动中。
作用是:模块加载系统再加载模块时,就知道了什么模块对应什么硬件设备。
用法是:MODULE_DEVICE_TABLE(设备类型,设备表)
设备类型:包括USB,PCI等,也可以自己起名字。
设备表:也是自己定义的,它的最后一项必须是空,用来标识结束。
*/
MODULE_DEVICE_TABLE(of, My_led_of_match);
static int My_led_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;//获取平台设备节点
enum of_gpio_flags flags;
int ret;
int en_value;
struct kobject * gpio_obj;
if (!node)return -ENODEV; //如果设备节点不存在,则返回 -ENODEV(设备不存在)
//从设备树中读取 my_led 的 GPIO 配置编号和标志
//设备树: led_ctr0 = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>;
My_led_ID = of_get_named_gpio_flags(node, "led_ctr0", 0, &flags);
//标志获取完之后,做一个三目运算,将 1 或者 0 赋值给 en_value
en_value = (flags == GPIO_ACTIVE_HIGH)? 1:0;
//gpio_is_valid 判断该 GPIO 编号是否有效,IO是否合法
if(!gpio_is_valid(My_led_ID)){
dev_err(&pdev->dev, "invalid power gpio%d\n", My_led_ID);
}
//devm_gpio_request 申请占用该 GPIO
ret = devm_gpio_request(&pdev->dev, My_led_ID, "my_gpio_led");
if (ret) {
dev_err(&pdev->dev,"failed to request GPIO%d for my_led \n",My_led_ID);
return -EINVAL;//返回 -EINVAL (模式值无效)
}
//gpio_direction_output 设置输出高还是低电平,根据上面的 三目运算,将 1 或者 0 赋值给 en_value
gpio_direction_output(My_led_ID, en_value);
//create 一个节点
gpio_obj = kobject_create_and_add("myled", NULL);
//如果是空的,则返回错误
if(gpio_obj == NULL){
printk("kobject_create_and_add error\n");
return -EINVAL;//返回 -EINVAL (模式值无效)
}
//
ret = sysfs_create_file(gpio_obj,&dev_attr_My_led_value.attr);
if (ret) {
printk("sysfs_create_file error\n");
return ret;
}
return 0;
}
static int My_led_remove(struct platform_device *pdev)
{
printk("My_led_remove");
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int My_led_suspend(struct device *dev)
{
printk("My_led_suspend");
return 0;
}
static int My_led_resume(struct device *dev)
{
printk("My_led_resume");
return 0;
}
#endif
static const struct dev_pm_ops My_led_pm_ops = {
#ifdef CONFIG_PM_SLEEP
.suspend = My_led_suspend,
.resume = My_led_resume,
.poweroff = My_led_suspend,
.restore = My_led_resume,
#endif
};
/* 定义一个平台设备 My_led_driver */
static struct platform_driver My_led_driver = {
.driver = {
.name = "my_led_ctr0",
.owner = THIS_MODULE,
.pm = & My_led_pm_ops,
.of_match_table = of_match_ptr(My_led_of_match),
},
.probe = My_led_probe,
.remove = My_led_remove,
};
module_platform_driver(My_led_driver);
MODULE_DESCRIPTION("power for My led gpio Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:_My_led_");
那么接下来是对Makefile
还有Kconfig
这两个文件的修改:
对Makefile
文件的修改添加:
obj-$(CONFIG_MY_GPIO) += my_gpio.o
对Kconfig`文件的修改添加:
config MY_GPIO
bool "my_led gpio driver"
default y //y 表示选上, n 表示没有选上
help
Enable this driver will support my_led control
在kernel目录下:make menuconfig
px30_android8.1/kernel$ make menuconfig
按步骤依次选择如下:
1、
2、
3、这里的 my_led gpio driver (NEW)
已经被选上。
4、Yes 选择一下
编译kernel:
px30_android8.1/kernel$ make ARCH=arm64 px30-evb-ddr3-v10-rk618-lvds-1920x1080.img && cd ..
在编译过程中我已经看到了(下面的最底部)
make[1]: 'include/generated/vdso-offsets.h' is up to date.
CHK include/generated/compile.h
GZIP kernel/config_data.gz
CHK kernel/config_data.h
UPD kernel/config_data.h
CC kernel/configs.o
LD kernel/built-in.o
CC drivers/misc/my_gpio.o //已经生成对应的my_gpio.o文件
使用ADB工具来调试:
echo 1 led
亮,echo 0 led
灭。
C:\Users\JLD\adb\platform-tools>adb shell
rk3326_m2g:/ $ su
rk3326_m2g:/ # echo 1 > sys/myled/My_led_value
rk3326_m2g:/ # echo 0 > sys/myled/My_led_value
对应的打印信息:
[ 232.206147] gpio_direction_output is 45
[ 235.526100] gpio_direction_output is 0
[ 638.359292] gpio_direction_output is 45
[ 649.008609] gpio_direction_output is 0
硬件原理图:
LED
是外接的一个小板