1、简介
rk3576+buildroot+设备树GPIO驱动编写。
个人理解设备树就相当于存在统一规则、统一管理的头文件,记录了开发板的设备信息。
2、设备树语法
2.1、dtsi 头文件
设备树也支持头文件,设备树的头文件扩展名为.dtsi
设备树文件不仅可以应用 C 语言里面的.h 头文件,甚至也可以引用.dts 文件。 因此在.dts 设备树文件中,可以通过"#include"来引用.h、 .dtsi 和.dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后缀。
一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等等。 比如 rk3576.dtsi 就是描述 RK3576 芯片本身的外设信息, 内容一看6000多行,不贴了,哈哈
2.2、设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键---值对。
//rk3576.dtsi
i2c2: i2c@2ac50000 {
compatible = "rockchip,rk3576-i2c", "rockchip,rk3399-i2c";
reg = <0x0 0x2ac50000 0x0 0x1000>;
clocks = <&cru CLK_I2C2>, <&cru PCLK_I2C2>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c2m0_xfer>;
resets = <&cru SRST_I2C2>, <&cru SRST_P_I2C2>;
reset-names = "i2c", "apb";
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
//rk3576-custom.dtsi
&i2c2 {
pinctrl-names = "default";
pinctrl-0 = <&i2c2m0_xfer>;
status = "okay";
gt911_dsi: gt911@5d {
status = "okay";
compatible = "goodix,gt911";
reg = <0x5d>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PD0 IRQ_TYPE_LEVEL_LOW>;
reset-gpios = <&gpio0 RK_PD1 GPIO_ACTIVE_LOW>;
irq-gpios = <&gpio0 RK_PD0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&tp0_irq>;
touchscreen-inverted-x = <1>;
touchscreen-swapped-x-y = <1>;
};
};
比如说&I2C2表示引用系统中定义好的 I²C 控制器节点,通常在 SoC 的 DTSI 文件中定义,gt911就是I2C2下的设备节点,可以看到节点的命名规则如下:
node-name@unit-address
但是上面还使用了label,
label: node-name@unit-address
引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点
每个节点都有不同的属性,不同的属性又有不同的内容,属性是键值对,值可以为空或者任意的字节流。
2.3、标准属性
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性, Linux 下的很多外设驱动都会使用这些标准属性,本节我们就来学习一下几个常用的标准属性。
2.3.1、compatible 属性
compatible 属性也叫做"兼容性"属性,这是非常重要的一个属性! compatible 属性的值是一个字符串列表, compatible 属性用于将设备和驱动绑定起来。
一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
比如上面使用的i2c2的节点属性
compatible = "rockchip,rk3576-i2c", "rockchip,rk3399-i2c";
表示芯片使用的是rockchip,驱动模块是rk3576-i2c,可以使用多个属性值,后面也有rk3399-i2c。之后使用的时候会在驱动文件里查找对应的属性,如果查找到了就会使用这个驱动文件
2.3.2、model属性
model 属性值也是一个字符串,一般 model 属性描述开发板的名字或者设备模块信息,但是没有实际用途,一般是起个名字
2.3.3、status 属性
status 属性看名字就知道是和设备状态有关的, status 属性值也是字符串,字符串是设备的状态信息,比如okay表示已启用,disable表示未启用不可操作
2.3.4、#address-cells 和#size-cells 属性
这两个属性的值都是无符号 32 位整形, #address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。 #address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位), #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。 #address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度, reg 属性的格式为:
reg = <address1 length1 address2 length2 address3 length3......>
每个"address length"组合表示一个地址范围,其中 address 是起始地址, length 是地址长度, #address-cells 表明 address 这个数据所占用的字长, #size-cells 表明 length 这个数据所占用的字长,比如:
#address-cells = <1>;
#size-cells = <0>;
说明节点 reg 属性中起始地址所占用的字长为 1,地址长度所占用的字长为 0。
所以当#size-cells = <1>,则表示节点起始地址长度所占用的字长为 1,地址长度所占用的字长也为 1。这时reg = <0xff4e0000 0x4000>;就表示设置了起始地址为 0xff4e0000,地址长度为 0x4000。
2.3.5、reg属性
reg 属性的值一般是(address, length)对。 reg 属性一般用于描述设备地址空间资源信息或者设备地址信息,比如某个外设的寄存器地址范围信息,或者 IIC器件的设备地址等
2.3.6、ranges属性
ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
- child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
- parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
- length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换
2.4、根节点compatible 属性
每个节点都有 compatible 属性,根节点"/"也不例外,比如说rk3576.dtsi中
compatible = "rockchip,rk3576";
Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。
2.5、节点的增改删除
比如说前文的I2C2里,就存在添加了子节点gt911_dsi,注意这里添加不要在rk3576.dtsi文件里添加,因为这里添加代表着全部使用rk3576的板子都会添加,需要添加在自己的设备树文件添加,就跟前面rk3576-custom.dtsi写的一样
2.6、特殊节点
在根节点"/"中有两个特殊的子节点: aliases 和 chosen,我们接下来看一下这两个特殊的子节点,
- aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上 label,然后通过&label 来访问节点。
- chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般.dts 文件中 chosen 节点通常为空或者内容很少
3、设备树常用的OF操作函数
设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。 Linux 内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀"of_",所以在很多资料里面也被叫做 OF 函数。这些 OF 函数原型都定义在 include/linux/of.h 文件中
3.1、查找节点的 OF 函数
of_find_node_by_name 函数
of_find_node_by_name 函数通过节点名字查找指定的节点,函数原型如下:
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
- from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
- name:要查找的节点名字。
- 返回值: 找到的节点,如果为 NULL 表示查找失败。
of_find_node_by_type 函数
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
通过 device_type 属性查找指定的节点
of_find_compatible_node 函数
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type,
const char *compat)
根据 device_type 和 compatible 这两个属性查找指定的节点
of_find_matching_node_and_match 函数
struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match)
通过 of_device_id 匹配表来查找指定的节点
of_find_node_by_path 函数
inline struct device_node *of_find_node_by_path(const char *path)
通过路径来查找指定的节点
3.2、查找父/子节点的 OF 函数
of_get_parent 函数
获取指定节点的父节点(如果有父节点的话),
struct device_node *of_get_parent(const struct device_node *node)
of_get_next_child 函数
用迭代的查找子节点
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev)
- node:父节点
- prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始
3.3、提取属性值的 OF 函数
of_find_property 函数
查找指定的属性
struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
- np:设备节点
- name:属性名字
- lenp:属性值的字节数
- 返回值: 找到的属性
of_property_count_elems_of_size 函数
获取属性中元素的数量,比如 reg 属性值是一个数组,那么使用此函数可以获取到这个数组的大小
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname,
int elem_size)
- np:设备节点
- proname: 需要统计元素数量的属性名字。
- elem_size:元素长度。
- 返回值: 得到的属性元素数量
of_property_read_u32_index 函数
从属性中获取指定标号的 u32 类型数据值(无符号 32位),比如某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值
int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index,
u32 *out_value)
- np:设备节点。
- proname: 要读取的属性名字
- index:要读取的值标号。
- out_value:读取到的值
- 返回值: 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
of_property_read_u8/16/32/64_array 函数
分别是读取属性中 u8、 u16、 u32 和 u64 类型的数组数据,比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据
int of_property_read_u8_array(const struct device_node *np,
const char *propname,
u8 *out_values,
size_t sz)
int of_property_read_u16_array(const struct device_node *np,
const char *propname,
u16 *out_values,
size_t sz)
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz)
int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,
size_t sz)
- np:设备节点
- proname: 要读取的属性名字。
- out_value:读取到的数组值,分别为 u8、 u16、 u32 和 u64。
- sz: 要读取的数组元素数量。
- 返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小
of_property_read_u8/16/32/64函数
这四个函数就是用于读取这种只有一个整形值的属性,分别用于读取 u8、 u16、 u32 和 u64 类型属性值
int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
int of_property_read_u64(const struct device_node *np,
const char *propname,
u64 *out_value)
- np:设备节点。
- proname: 要读取的属性名字。
- out_value:读取到的数组值。
- 返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
of_property_read_string 函数
用于读取属性中字符串值
int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string)
- np:设备节点。
- proname: 要读取的属性名字。
- out_string:读取到的字符串值。
- 返回值: 0,读取成功,负值,读取失败
of_n_addr_cells 函数
获取#address-cells 属性值,
int of_n_addr_cells(struct device_node *np)
- np:设备节点。
of_n_size_cells 函数
获取#size-cells 属性值
int of_n_size_cells(struct device_node *np)
- np:设备节点。
3.4、其他常用的 OF 函数
of_device_is_compatible 函数
用于查看节点的 compatible 属性是否有包含 name 指定的字符串,也就是检查设备节点的兼容性
int of_device_is_compatible(const struct device_node *device,const char *name)
函数参数和返回值含义如下:
- device:设备节点。
- name:要查看的字符串。
- 返回值: 0,节点的 compatible 属性中不包含 name 指定的字符串;正数,节点的 compatible属性中包含 name 指定的字符串。
of_get_address 函数
用于获取地址相关属性,主要是"reg"或者"assigned-addresses"属性值
const __be32 *of_get_address(struct device_node *dev,
int index,
u64 *size,
unsigned int *flags)
|---------------|
| 函数参数和返回值含义如下: |
- 函数参数和返回值含义如下
- dev:设备节点。
- index:要读取的地址标号。
- size:地址长度。
- flags:参数,比如 IORESOURCE_IO、 IORESOURCE_MEM 等
- 返回值: 读取到的地址数据首地址,为 NULL 的话表示读取失败。
of_translate_address 函数
负责将从设备树读取到的物理地址转换为虚拟地址
u64 of_translate_address(struct device_node *dev,const __be32 *addr)
函数参数和返回值含义如下
- dev:设备节点。
- in_addr:要转换的地址。
- 返回值: 得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。
of_address_to_resource 函数
提取 reg 属性值,然后将其转换为 resource 结构体类型
int of_address_to_resource(struct device_node *dev,
int index,
struct resource *r)
函数参数和返回值含义如下:
- dev:设备节点。
- index:地址资源标号。
- r:得到的 resource 类型的资源值。
- 返回值: 0,成功;负值,失败。
of_iomap 函数
of_iomap 函数用于直接内存映射,以前我们会通过 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址,不需要使用 ioremap 函数了
void __iomem *of_iomap(struct device_node *np,int index)
- 参数和返回值含义如下:
- np:设备节点。
- index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
- 返回值: 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。
4、设备树驱动
4.1、在设备树文件中创建相应的设备节点
rk3576_gpio{
#address-cells = <1>;
#size-cells = <1>;
compatible = "rk3576_gpio_test";
reg = <0x0 0x2AE40000 0x0 0x00000004
0x0 0x2AE40008 0x0 0x00000004
0x0 0x26044084 0x0 0x00000004
0x0 0x26044140 0x0 0x00000004
0x0 0x26046140 0x0 0x00000004>;
status = "okay";
};
4.2、在驱动文件里使用设备树
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
//Pin Name是GPIO4_A4
#define DEV_CNT 1
#define DEV_NAME "dtsgpio"
#define OUTPUT_HIGH 1
#define OUTPUT_LOW 0
static void __iomem *pVCCIO_IOC_GPIO4A_PULL;
static void __iomem *pVCCIO_IOC_GPIO4A_DS_H;
static void __iomem *pTOP_IOC_GPIO4A_IOMUX_SEL_H;
static void __iomem *pGPIO4_DR_L;
static void __iomem *pGPIO4_DDR_L;
struct dtsgpio_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd;/* 设备节点 */
};
struct dtsgpio_dev dtsgpio;
void gpio_switch(u8 sta)
{
u32 val = 0;
if(sta == OUTPUT_HIGH) {
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4); /* bit0 清零*/
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, pGPIO4_DR_L);
}else if(sta == OUTPUT_LOW) {
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4); /* bit0 清零*/
val |= ((0X1 << 20) | (0X0 << 4));
writel(val, pGPIO4_DR_L);
}
}
static ssize_t gpio_chrdev_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
printk("gpio_chrdev_read!\r\n");
return 0;
}
static ssize_t gpio_chrdev_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char stat;
printk("gpio_chrdev_write!\r\n");
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
stat = databuf[0]; /* 获取状态值 */
printk("gpio_chrdev_write,stat = %d\r\n", stat);
if(stat == OUTPUT_HIGH) {
printk("gpio_chrdev_write,OUTPUT_HIGH\r\n");
gpio_switch(OUTPUT_HIGH);
} else if(stat == OUTPUT_LOW) {
printk("gpio_chrdev_write,OUTPUT_LOW\r\n");
gpio_switch(OUTPUT_LOW);
}
return 0;
}
static int gpio_chrdev_open(struct inode *inode, struct file *filp)
{
printk("gpio_chrdev_open!\r\n");
filp->private_data = &dtsgpio; /* 设置私有数据 */
return 0;
}
static int gpio_chrdev_release(struct inode *inode, struct file *filp)
{
printk("gpio_chrdev_release!\r\n");
return 0;
}
static struct file_operations gpio_chrdev_fops = {
.owner = THIS_MODULE,
.open = gpio_chrdev_open,
.release = gpio_chrdev_release,
.write = gpio_chrdev_write,
.read = gpio_chrdev_read,
};
void gpio_unmap(void)
{
/* 取消映射 */
iounmap(pVCCIO_IOC_GPIO4A_PULL);
iounmap(pVCCIO_IOC_GPIO4A_DS_H);
iounmap(pTOP_IOC_GPIO4A_IOMUX_SEL_H);
iounmap(pGPIO4_DR_L);
iounmap(pGPIO4_DDR_L);
}
static int __init gpio_dts_init(void)
{
u32 val = 0;
int ret;
u32 regdata[16];
const char *str;
struct property *proper;
dtsgpio.nd = of_find_node_by_path("/rk3576_gpio");
if(dtsgpio.nd == NULL) {
printk("rk3576_gpio node not find!\r\n");
return 1;
} else {
printk("rk3576_gpio node find!\r\n");
}
/* 2、获取 compatible 属性内容 */
proper = of_find_property(dtsgpio.nd, "compatible", NULL);
if(proper == NULL) {
printk("compatible property find failed\r\n");
return 1;
} else {
printk("compatible = %s\r\n", (char*)proper->value);
}
/* 3、获取 status 属性内容 */
ret = of_property_read_string(dtsgpio.nd, "status", &str);
if(ret < 0){
printk("status read failed!\r\n");
return 1;
} else {
printk("status = %s\r\n",str);
}
/* 4、获取 reg 属性内容 */
ret = of_property_read_u32_array(dtsgpio.nd, "reg", regdata, 10);
if(ret < 0) {
printk("reg property read failed!\r\n");
return 1;
} else {
u8 i = 0;
printk("reg data:\r\n");
for(i = 0; i < 10; i++)
printk("%#X ", regdata[i]);
printk("\r\n");
}
pGPIO4_DR_L = of_iomap(dtsgpio.nd, 0);
pGPIO4_DDR_L = of_iomap(dtsgpio.nd, 1);
pTOP_IOC_GPIO4A_IOMUX_SEL_H = of_iomap(dtsgpio.nd, 2);
pVCCIO_IOC_GPIO4A_DS_H = of_iomap(dtsgpio.nd, 3);
pVCCIO_IOC_GPIO4A_PULL = of_iomap(dtsgpio.nd, 4);
val = readl(pTOP_IOC_GPIO4A_IOMUX_SEL_H);
printk("of_iomap 7!\r\n");
val &= ~(0XF << 0);
val |= ((0XF << 16) | (0X0 << 0));
printk("of_iomap 8!\r\n");
writel(val, pTOP_IOC_GPIO4A_IOMUX_SEL_H);
printk("of_iomap 9!\r\n");
/* 设置 GPIO4_A4 驱动能力为 level5 */
val = readl(pVCCIO_IOC_GPIO4A_DS_H);
printk("of_iomap 10!\r\n");
val &= ~(0X7 << 0);
val |= ((0X7 << 16) | (0X5 << 0));
writel(val, pVCCIO_IOC_GPIO4A_DS_H);
/* 设置上下拉 */
val = readl(pVCCIO_IOC_GPIO4A_PULL);
val &= ~(0X3 << 8);
val |= ((0X3 << 24) | (0X1 << 8));
writel(val, pVCCIO_IOC_GPIO4A_PULL);
/* 设置为输出 */
val = readl(pGPIO4_DDR_L);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, pGPIO4_DDR_L);
/* 设置 默认输出低电平 */
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X0 << 4));
writel(val, pGPIO4_DR_L);
printk("of_iomap 11!\r\n");
ret = alloc_chrdev_region(&dtsgpio.devid,0,DEV_CNT,DEV_NAME);
if(ret < 0){
printk("%s alloc_chrdev_region fail,ret = %d\r\n", DEV_NAME,ret);
return 1;
}
dtsgpio.major = MAJOR(dtsgpio.devid);/* 获取主设备号 */
dtsgpio.minor = MINOR(dtsgpio.devid);/* 获取次设备号 */
printk("dtsgpio.devid=%d\r\n",dtsgpio.devid);
printk("dtsgpio major=%d,minor=%d\r\n",dtsgpio.major,dtsgpio.minor);
cdev_init(&dtsgpio.cdev, &gpio_chrdev_fops);
dtsgpio.cdev.owner = THIS_MODULE;
ret = cdev_add(&dtsgpio.cdev, dtsgpio.devid,DEV_CNT);
if(ret < 0){
printk("%s cdev_add fail,ret = %d\r\n", DEV_NAME,ret);
return 1;
}
/* 4、创建类 */
dtsgpio.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(dtsgpio.class)) {
printk("%s class_create fail\r\n", DEV_NAME);
return 1;
}
/* 5、创建设备 */
dtsgpio.device = device_create(dtsgpio.class,
NULL,
dtsgpio.devid,
NULL,
DEV_NAME);
if (IS_ERR(dtsgpio.device)) {
printk("%s device_create fail\r\n", DEV_NAME);
return 1;
}
return 0;
}
module_init(gpio_dts_init);
static void __exit gpio_dts_exit(void)
{
/* 取消映射 */
gpio_unmap();
/* 注销字符设备驱动 */
cdev_del(&dtsgpio.cdev);/* 删除 cdev */
unregister_chrdev_region(dtsgpio.devid, DEV_CNT);
device_destroy(dtsgpio.class, dtsgpio.devid);
class_destroy(dtsgpio.class);
}
module_exit(gpio_dts_exit);
MODULE_LICENSE("GPL");
4.3、应用程序App
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
printf("led_tiny test\n");
/*判断输入的命令是否合法*/
if(argc != 2)
{
printf(" command error ! \n");
printf(" usage : sudo test_app num [num can be 0 or 1]\n");
return -1;
}
/*打开文件*/
int fd = open("/dev/dtsgpio", O_RDWR);
if(fd < 0)
{
printf("open file : %s failed !\n", argv[0]);
return -1;
}
unsigned char command = atoi(argv[1]); //将受到的命令值转化为数字;
/*写入命令*/
int error = write(fd,&command,sizeof(command));
if(error < 0)
{
printf("write file error! \n");
close(fd);
/*判断是否关闭成功*/
}
/*关闭文件*/
error = close(fd);
if(error < 0)
{
printf("close file error! \n");
}
return 0;
}