本人
linux
驱动小白,文章基于B站up主 李Sir______ 视频内容记录,做笔记用。如有错误欢迎指正。本文将以开发板第40
引脚GPIO3_B4
作为LED
灯珠的控制引脚,高电平灯亮,低电平灯灭。
杂话
在linux
内核中,芯片厂商已经把所有控制器的设备树编写好了。硬件层与子系统的API
也都适配好无需使用者关心。我们编写驱动只需要将自己所需要的硬件资源与厂商定义好的设备树匹配,在驱动程序中调用相应子系统API
函数即可写出一份自己的GPIO
驱动程序。相比传统无官方库支持的单片机需操作寄存器来操作底层,程序可移植性大大提高。
内核子系统三层概念
c
user 逻辑代码
---------------------------------------------------------------------------------------------------------------------------------------
设备驱动层(今天的驱动在这一层) 通过各个子系统 完成对硬件的操作 并向上层提供接口 驱动工程师
______________________________________________________________________________
kernel
核心层(内核维护者) 屏蔽底层细节 向上层提供接口 各类子系统api 内核工程师
---------------------------------------------------------------------------------------------------------------------
厂商驱动层(瑞芯微) 各个厂商针对不同硬件的操作 包括内存映射 厂商
---------------------------------------------------------------------------------------------------------------------------------------
HARDWARE
motor led lcd ...
厂商的设备树文件
厂商已经把所有的控制器设备树信息都提供了,下面是与gpio
相关的一些文件所在位置。
c
path:kenel\arch\arm64\boot\dts\rockchip\rk3568.dtsi
...
gpio3: gpio@fe760000 {//节点名
compatible = "rockchip,gpio-bank"; //厂商名 设备名
reg = <0x0 0xfe760000 0x0 0x100>; //地址 0x0 0xfe760000控制器地址 0x0 0x100地址范围
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;//中断
clocks = <&cru PCLK_GPIO3>, <&cru DBCLK_GPIO3>;//时钟
gpio-controller;//标识
#gpio-cells = <2>;//描述子节点个数
gpio-ranges = <&pinctrl 0 96 32>; //引脚范围 &pinctrl 引用pinctrl节点 0 96 GPIO起始序号 32引脚范围
interrupt-controller;
#interrupt-cells = <2>;//描述子节点个数
};
...
//一些宏厂商也已经做出定义
path:kernel\include\dt-binding\pinctrl\rockchip.h
path:kernel\include\dt-binding\pinctrl\pinctrl-amd.h
path:kernel\include\dt-binding\gpio\gpio.h
gpio设备树书写规则
c
[names]-gpios=<控制器地址 引脚编号 高/低电平>;
设备树中添加我们的设备
仿照设备树中的节点,添加一个我们自己的节点,位置在根节点处
c
path:kenel\arch\arm64\boot\dts\tspi-rk3566-user-v10-linux.dts
..............
/ {
model = "lckfb tspi V10 Board";
compatible = "lckfb,tspi-v10", "rockchip,rk3566";
//自己新建的led灯的设备树
image_led{
compatible = "image,led";
status = "okay";
led_gpio=<&gpio3 RK_PB4 GPIO_ACTIVE_LOW>;
};
rk_headset: rk-headset {
compatible = "rockchip_headset";
headset_gpio = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>;
pinctrl-names = .......
.....
编译内核,将boot.img文件烧录到开发板
c
./build.sh kernel
查看添加的设备树节点是否存在
c
root@RK356X:/tmp# ls /proc/device-tree
...........
iep@fdef0000 serial@fe6c0000
image_led serial@fe6d0000
interrupt-controller@fd400000 sfc@fe300000
..........
GPIO相关的API
c
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)
功能:获取gpio编号
参数:
np 结构体指针
propname 属性名
index 编号
返回值:
成功 返回gpio编号
失败 返回错误码
static inline int gpio_request(unsigned gpio, const char *label)
功能:申请gpio
参数:
gpio GPIO编号
label 标签 NULL
返回值:
成功 返回0
失败 返回错误码
static inline int gpio_direction_input(unsigned gpio)
功能:设置gpio为输入
参数:
gpio GPIO编号
返回值:
成功 返回0
失败 返回错误码
static inline int gpio_direction_output(unsigned gpio, int value)
功能:设置gpio为输出
参数:
gpio GPIO编号
value 输出电平 1高电平 0是低电平
返回值:
成功 返回0
失败 返回错误码
static inline void gpio_set_value(unsigned int gpio, int value)
功能:设置gpio输出电平
参数:
gpio GPIO编号
value 输出电平 1高电平 0是低电平
返回值:
成功 返回0
失败 返回错误码
static inline int gpio_get_value(unsigned int gpio)
功能:获取gpio电平状态
参数:
gpio GPIO编号
返回值:
1 是高电平
0 是低电平
static inline void gpio_free(unsigned gpio)
功能:释放gpio
参数:
gpio GPIO编号
返回值:
无
解析设备树相关API
c
device_node 结构体讲解
struct device_node {
const char *name; //节点名
#const char *type; //节点类型
#phandle phandle; //唯一标识
const char *full_name; //节点全名
#struct fwnode_handle fwnode; //用于支持不同设备
struct property *properties; //设备数属性键值对的链表
#struct property *deadprops; /* removed properties */ 链表头
struct device_node *parent; //父节点
struct device_node *child; //子节点
struct device_node *sibling; //兄弟节点
#if defined(CONFIG_OF_KOBJ)
#struct kobject kobj;//内核对象
#endif
#unsigned long _flags;//状态标志
#void *data;//节点相关的私有数据
#if defined(CONFIG_SPARC)
#const char *path_component_name;//表示设备节点路径的一部分
#unsigned int unique_id;//唯一ID
#struct of_irq_controller *irq_trans;//中断控制器结构体指针
#endif
};
property结构体
struct property {
char *name;//键的名字
int length; //值长度
void *value; //值 注意 强转
struct property *next; //下一个键值对的property结构体指针
};
static inline struct device_node *of_find_node_by_path(const char *path)
功能:通过路径获取节点
参数:
path 路径 "/image_led"
返回值:
成功 返回结构体首地址
失败 返回NULL
static inline struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
功能:通过节点名获取节点
参数:
from 填NULL 从根节点搜索
name 节点名
返回值:
成功 返回结构体首地址
失败 返回NULL
static inline struct device_node *of_find_compatible_node(struct device_node *fromconst char *type,const char *compat)
功能:通过compatible获取节点
参数:
from 填NULL 从根节点搜索
type 填NUL
compat 厂商名 设备名
返回值:
成功 返回结构体首地址
失败 返回NULL
获取属性相关的API
static inline struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
功能:获取键值对
参数:
np device_node结构体首地址
name 键的名字
lenp 值的长度 单位字节
返回值:
成功 返回结构体首地址
失败 返回NULL
static inline int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
功能:获取单字节数据数组
参数:
np device_node结构体首地址
propname 键的名字
out_values 获取到的数据
sz 值的个数
返回值:
成功 返回0
失败 返回错误码
static inline int of_property_read_u32_index(const struct device_node *np,const char *propname, u32 index, u32 *out_value)
功能:获取32位数值
参数:
np device_node结构体首地址
propname 键的名字
index 索引号 下标
out_values 获取到的数据
返回值:
成功 返回0
失败 返回错误码
static inline int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string)
功能:获取字符串
参数:
np device_node结构体首地址
propname 键的名字
index 索引号 下标
out_string 获取到的字符串
返回值:
成功 返回0
失败 返回错误码
编写的驱动文件
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
//字符设备名 sys/class 描述名称
#define CDEV_NAME "image_led"
//设备树中 自定义led节点路径
#define LED_NODE_PATH "/image_led"
//自定义设备树节点中 led引脚名称
#define LED_NODE_PROPERTY "led_gpio"
int major = -1;
int gpionum = -1;
struct class *cls = NULL;
struct device *dev = NULL;
struct device_node *led_node = NULL; //设备树节点结构体
char kbuf[128] = {0};
int my_led_open(struct inode *inode, struct file *file)
{
printk("device:image_led is open\r\n");
return 0;
}
ssize_t my_led_read (struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{
char level = 0;
if(size > 1)
{
size = 1;
}
level = gpio_get_value(gpionum);
if(copy_to_user(ubuf, &level, size))
{
printk("copy data from user failed!\n");
return -EINVAL;
}
printk("device:image_led cur level is %d\r\n",level);
return size;
}
ssize_t my_led_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
size = size > sizeof(kbuf) ? sizeof(kbuf) : size;
if(copy_from_user(kbuf, ubuf, size))
{
printk("copy data from user failed!\n");
return -EINVAL;
}
if(kbuf[0] == '1')
{
gpio_set_value(gpionum,1); //设置高电平
printk("device:image_led set level is %d\r\n",kbuf[0]);
}
else
{
gpio_set_value(gpionum,0); //设置低电平
printk("device:image_led set level is %d\r\n",kbuf[0]);
}
return size;
}
int my_led_close (struct inode *inode, struct file *file)
{
printk("device:image_led is close\r\n");
return 0;
}
struct file_operations fops = {
.open = my_led_open,
.read = my_led_read,
.write = my_led_write,
.release = my_led_close
};
int led_gpio_init(void)
{
//读取设备树上节点路径获取节点数据
led_node = of_find_node_by_path(LED_NODE_PATH);
if(led_node == NULL)
{
printk("find node %s failed!\n",LED_NODE_PATH );
return -ENODEV;
}
//通过节点数据中的名称获取gpio编号
gpionum = of_get_named_gpio(led_node, LED_NODE_PROPERTY, 0);
if(gpionum < 0)
{
printk("get property %s failed!\n",LED_NODE_PROPERTY );
return -EINVAL;
}
//申请GPIO 防止占用冲突
if(gpio_request(gpionum,NULL))
{
printk("request gpio failed!\n");
return -EINVAL;
}
//设置GPIO为输出
if(gpio_direction_output(gpionum, 0))
{
printk("set gpio direction failed!\n");
return -EINVAL;
}
return 0;
}
void led_gpio_deinit(void)
{
gpio_set_value(gpionum,0);
gpio_free(gpionum);
}
static int __init image_led_init(void)
{
//注册字符设备
major = register_chrdev(0,CDEV_NAME,&fops);
if(major < 0)
{
printk("register chardev failed! \n");
return -1;
}
//自动创建设备节点
//提交目录信息
cls = class_create(THIS_MODULE,CDEV_NAME);
if(IS_ERR(cls))
{
printk("auto create node failed! \n");
goto del_cdev; //
}
//提交设备信息
dev = device_create(cls,NULL,MKDEV(major,0),NULL,"image_led0");
if(IS_ERR(dev))
{
printk("device create failed! \n");
goto destroy_class;
}
if(led_gpio_init()<0)
{
goto free_gpio;
}
return 0;
free_gpio:
led_gpio_deinit();
destroy_class:
class_destroy(cls);
del_cdev:
unregister_chrdev(major,CDEV_NAME);
return -EIO;
}
static void __exit image_led_exit(void)
{
led_gpio_deinit();
device_destroy(cls, MKDEV(major,0));
class_destroy(cls);
unregister_chrdev(major,CDEV_NAME);
}
module_init(image_led_init);
module_exit(image_led_exit);
MODULE_LICENSE("GPL");
APP测试文件
c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#define MY_CHRDEV_NAME "/dev/image_led0"
int fd = 0;
int main(void)
{
char light_cmd = '1';
char off_cmd = '0';
char count = 0;
char read_buf[1] = {0};
fd = open(MY_CHRDEV_NAME,O_RDWR);
if(fd < 0)
{
printf("open %s is failed fd:%d\r\n",MY_CHRDEV_NAME,fd);
return -1;
}
for(count=0;count<10;count++)
{
write(fd,&light_cmd,1);
printf("cmd:%c\r\n",light_cmd);
usleep(500*1000);
read(fd,read_buf,1);
printf("read gpio level is %d\r\n",read_buf[0]);
write(fd,&off_cmd,1);
printf("cmd:%c\r\n",off_cmd);
usleep(500*1000);
read(fd,read_buf,1);
printf("read gpio level is %d\r\n",read_buf[0]);
}
close(fd);
return 0;
}
Makefile文件
c
PWD ?= $(shell pwd)
KERNELDIR:=/home/image/work/tspi/sdk/kernel
CROSS_COMPILE ?= /home/image/work/tspi/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
obj-m += demo.o
CC := $(CROSS_COMPILE)gcc
module:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 modules
$(CC) test_app.c -o test_app
clean:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 clean
rm test_app
APP执行效果
c
root@RK356X:/tmp# ./test_app
cmd:1
read gpio level is 1
cmd:0
read gpio level is 0
cmd:1
read gpio level is 1
cmd:0
read gpio level is 0
cmd:1
read gpio level is 1
cmd:0
read gpio level is 0
cmd:1
..............
开发板打印调试信息
c
root@RK356X:/tmp# dmesg
[ 6757.176165] device:image_led is open
[ 6757.176265] device:image_led set level is 49
[ 6757.676878] device:image_led cur level is 1
[ 6757.677111] device:image_led set level is 48
[ 6758.177583] device:image_led cur level is 0
[ 6758.177804] device:image_led set level is 49
[ 6758.678220] device:image_led cur level is 1
[ 6758.678423] device:image_led set level is 48
[ 6759.178801] device:image_led cur level is 0
[ 6759.178988] device:image_led set level is 49
[ 6759.679358] device:image_led cur level is 1
[ 6759.679582] device:image_led set level is 48
[ 6760.179943] device:image_led cur level is 0
[ 6760.180143] device:image_led set level is 49
[ 6760.680495] device:image_led cur level is 1
[ 6760.680716] device:image_led set level is 48
[ 6761.181149] device:image_led cur level is 0
[ 6761.181349] device:image_led set level is 49
[ 6761.681747] device:image_led cur level
..................
//48 49为ASCII码的0 和 1这里打印没处理好
开发板运行效果
开发板运行效果