pinctrl 和 gpio 子系统的字符设备驱动
一、 修改设备树文件
打开 imx6ull-alientekemmc.dts,在根节点“/”下创建 LED 灯节点,节点名为“gpioled”。
/*yqh2021/5/12*/
gpioled{
compatible = "alientek,gpioled";
pinctrl-name = "default";
pinctrl-0 = <&pinctrl_gpioled>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
打开 imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点。
/*yqh2021/5/12*/
pinctrl_gpioled: ledgrp{
fsl,pihs = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0
>;
};
在 imx6ull-alientek-emmc.dts 中找到如下内容,屏蔽gpio1_3
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
/*YQH 2021/5/12*/
/*MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0*/
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
>;
};
&tsc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
/*YQH 2021/5/12*/
/*xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;*/
measure-delay-time = <0xffff>;
pre-charge-time = <0xfff>;
status = "okay";
};
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。
使用新生成的设备树 进入查看节点是否存在。
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/yqh/linux/tftpboot/ -f
cd /proc/device-tree/
ls
二、 LED 灯驱动程序编写
/*参照linux内核去写驱动*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kernel.h> //printk需要包含的头文件
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*注册字符设备个数*/
#define GPIOLED_CNT 1
/*注册字符设备名字*/
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/*创建gpioled设备结构体*/
struct gpioled_dev{
dev_t devid; //设备号
int major;
int minor;
struct cdev cdev; //字符设备号
struct class *class; //类
struct device *device; //类下创建设备
struct device_node *nd; //设备节点
int led_gpio; //gpio编号
};
/*定义结构体变量*/
struct gpioled_dev gpioled;
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data=&gpioled;
//在 open 函数里面设置好私有数据以后,在 write、read、close 等函数中直接读取 private_data即可得到设备结构体
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
/*
struct dtsled_dev *dev=(struct dtsled_dev*)filp->private_data; //结构体强制转换
dev->device //这样就可以通过dev进行访问私有数据里的成员变量
*/
return 0;
}
/*定义字符驱动操作集*/
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
//.read = led_read,
.write = led_write,
.release = led_release,
};
/*驱动入口函数*/
static int __init led_init(void)
{
int ret=0; //用来接收错误
/*注册字符设备驱动*/
gpioled.major = 0; //由系统分配
if(gpioled.major) //给定主设备号的情况
{
gpioled.devid=MKDEV(gpioled.major,0);
register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME);
}else //没有给定设备号
{
ret=alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); //申请设备号
gpioled.major=MAJOR(gpioled.devid); //获取分配的主设备号
gpioled.minor=MINOR(gpioled.devid); //获取分配的次设备号
}
if(ret<0)
{
printk("dtsled chrdev_region error!\r\n");
goto failed_devid;
}
printk("gpioled major=%d,minor=%d\r\n",gpioled.major,gpioled.minor);
/*初始化cdev*/
gpioled.cdev.owner = THIS_MODULE;
/*初始化字符设备需要定义:定义字符设备,定义操作函数*/
cdev_init(&gpioled.cdev,&gpioled_fops);
/*添加cdev 字符设备添加进内核*/
ret=cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);
if(ret<0) //添加失败在goto语句里释放设备号
{
goto failed_cdev;
}
/*创建类*/
/*自动创建设备节点通过class和device*/
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class)) {
return PTR_ERR(gpioled.class);
goto failed_class;
}
/*创建设备*/
gpioled.device = device_create(gpioled.class, NULL,gpioled.devid, NULL, GPIOLED_NAME);
//1.类2.父设备为NULL3.设备号4.设备使用数据一般为NULL5.设备名
if (IS_ERR(gpioled.device)) {
return PTR_ERR(gpioled.device);
goto failed_device;
}
/*获取设备节点从设备树*/
gpioled.nd=of_find_node_by_path("/gpioled");
if(gpioled.nd==NULL) {
ret=-EINVAL;
goto fail_findnode;
}
/*获取设备树中的 gpio 属性,得到LED所使用的LED编号*/
gpioled.led_gpio=of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
printk("can't get led-gpio");
ret=-EINVAL;
goto fail_findnode;
}
printk("led-gpio num = %d\r\n", gpioled.led_gpio);
/*申请IO 如果申请失败说明IO被占用*/
if(ret){
printk("Failed to request the led gpio\r\n");
ret=-EINVAL;
goto fail_findnode;
}
/*使用IO 设置为输出 并且输出高电平,默认关闭LED灯*/
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
goto fail_setoutput;
}
/*输出低电平点灯*/
//gpio_set_value(gpioled.led_gpio,0);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
device_destroy(gpioled.class,dtsled.devid);
failed_device:
class_destroy(gpioled.class);
failed_class:
cdev_del(&gpioled.cdev);
failed_cdev:
unregister_chrdev_region(gpioled.devid, DTSLED_CNT);
failed_devid:
return ret;//没有申请什么所以返回一个返回值不需要释放操作
}
/*驱动出口函数*/
static void __exit led_exit(void)
{
/*注销字符设备驱动*/
cdev_del(&gpioled.cdev);
/*释放设备号*/
unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
/*注销设备*/
device_destroy(gpioled.class, gpioled.devid);
/*注销类*/
class_destroy(gpioled.class);
/*释放注册IO*/
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YQH");
三、makefile
KERNELDIR := /home/yqh/linux/IMX6ULL/linux_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m :=gpioled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
make -j32
编译成功以后就会生成一个名为“gpioled.ko”的驱动模块文件
sudo cp gpioled.ko /home/yqh/linux/nfs/rootfs/lib/modules/4.1.15/ -f
四、应用层代码
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#define LEDOFF 0
#define LEDON 1
/*argc应用程序参数个数
*argv[]:具体的参数内容,字符串形式
*./ledAPP <filename> <0:1> 1表示关灯1表示开灯
*./ledAPP /dev/dtsled 0 关灯
*./ledAPP /dev/dtsled 1 开灯
*1执行读,在应用层打印出来读得数据 2执行写,通过调用chrdevbase_write离得打印语句而打印出来
*/
int main(int argc,char *argv[])
{
int fd;
int retvalue;
char *filename;
unsigned char databuf[1];
if(argc !=3)
{
printf("Error Usage\r\n");
return -1;
}
filename=argv[1];
fd=open(filename,O_RDWR);
if(fd<0)
{
printf("file %s open failed!\r\n",filename);
return -1;
}
databuf[0]=atoi(argv[2]); //将字符转化为数字
retvalue=write(fd,databuf,sizeof(databuf));
if(retvalue<0)
{
printf("LED Control Failed\r\n");
//colse(fd);
return -1;
}
retvalue=close(fd);
if(retvalue<0)
{
printf("file %s close failed!\r\n",filename);
return -1;
}
return 0;
}
运行测试
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
编译成功以后就会生成 ledApp 这个应用程序。
将编译出来的 gpioled.ko 和 ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入如下命令加载 gpioled.ko 驱动
模块:
cd lib/modules/4.1.15/
depmod
modprobe gpioled.ko
./ledApp /dev/gpioled 1 //打开 LED 灯
./ledApp /dev/gpioled 0 //关闭 LED 灯