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);elsereturn 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_valueen_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 申请占用该 GPIOret = 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_valuegpio_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_GPIObool "my_led gpio driver"default y //y 表示选上, n 表示没有选上helpEnable 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.hGZIP kernel/config_data.gzCHK kernel/config_data.hUPD kernel/config_data.hCC kernel/configs.oLD kernel/built-in.oCC 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
是外接的一个小板