linux 驱动开发 pinctrl 和 gpio 子系统驱动

以蜂鸣器驱动为例

1 确定引脚名称

SNVS_TAMPER1

2在设备树中添加pinctrl 信息

在 dts文件中找到iomuxc,在imx6ul-evk子节点下,添加下面信息。

 pinctrl_beep: beepgrp { 
        fsl,pins = < 
         MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0   /* beep */
         >;
  };

MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01

#define MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01  0x000C 0x0050 0x0000 0x5 0x0

作用是将SNVS_TAMPER1这个引脚配置为 GPIO5_IO01

分别为mux_reg conf_reg input_reg mux_mode input_val

0x000c mux_reg   寄存器偏移地址

即配置引脚复用寄存器的偏移地址 

0x0050 conf_reg 寄存器偏移地址

即配置引脚电气属性的偏移地址

0x0000 input_reg 寄存器偏移地址

input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的 外设需要配置 input_reg 寄存器。没有的话就不需要设置,UART1_RTS_B 这个 PIN 在做 GPIO1_IO19 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的。

0x5 mux_reg 寄 存 器 值

即设置到复用寄存器里的值  0x5,表示设置为gpio5-io1

0x0 input_reg 寄存器值

这里无效

0x10B0

用来配置电气属性的,即上下拉,引脚速度等

对应 conf_reg 寄存器的值

10B0对应 0001 0000 1011 0000

对应下图寄存器设置,高16位为保留位,均位0

  1. "HYS": "Hyst. Enable Field,选择一个值来启用或禁用迟滞(hysteresis)。迟滞是一种现象,其中输入信号在阈值附近反复变化时,输出信号以更稳定的方式变化。", (0, 即禁用)
  2. "PUS": "Pull Up / Down Config. Field,上拉或下拉电阻配置字段。(00,即100k的下拉电阻)
  3. "PUE": "Pull / Keep Select Field,上拉或保持选择字段。上拉通常指的是将信号线拉升到高电平(逻辑1),而保持则指的是将信号线保持在中间电平(逻辑0)(0,保持)
  4. "PKE": "Pull / Keep Enable Field,上拉或保持使能字段 (1,使能)
  5. "ODE": "Open Drain Enable Field,开漏使能字段。(0,不开启开漏)                                 (000,保留三位)
  6. "SPEED": "Speed Field,速度字段。 这个速度通常与信号的频率或传输速率有关(10,100mhz)
  7. "DSE": "Drive Strength Field,驱动强度字段。(110,R0/6的驱动能力)  (00,保留两位)
  8. "SRE": "Slew Rate Field,斜率速率字段。斜率速率通常指的是信号从一种状态切换到另一种状态所需的时间  (0,慢速率)

3创建蜂鸣器节点,加入gpio信息

在根节点下添加beep节点

	beep {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "atkalpha-beep";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_beep>;
		beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};

  1. '#address-cells': 该属性表示在设备地址中使用的单元数量。在这种情况下,它是1,表示设备地址有一个单元。
  2. '#size-cells': 该属性表示在设备大小中使用的单元数量。在这种情况下,它也是1,表示设备大小有一个单元。
  3. 'compatible': 该属性定义了设备的兼容性字符串。在这种情况下,它是'atkalpha-beep',表示该设备是ATK Alpha Beep设备。
  4. 'pinctrl-names': 该属性定义了设备使用的pin control的名称。在这种情况下,它只有一个名为'default'的pin control。
  5. 'pinctrl-0': 该属性定义了设备使用的第一个pin control。链接到上一步设置的在iomux下的 pinctrl_beep节点上
  6. 'beep-gpio': 该属性定义了用于控制beep设备的GPIO。在这种情况下,它使用的是名为'gpio5'的GPIO,且是gpio1 并且当GPIO处于高电平时,beep设备会被激活。
  7. 'status': 该属性表示设备的状态。在这种情况下,设备的状态是'okay',表示设备可以正常工作。

4检测PIN是否被其他外设引用

本次使用的引脚为SNVS_TAMPER1,先检查 PIN 为 SNVS_TAMPER1 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO5_IO01 这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。

使用make dtbs指令编译设备树,将编译后的文件下载到系统中

进入“/proc/device-tree”查看beep节点是否存在。

如果存在就表示设备树添加成功。

5驱动程序编写

1 创建vscode工程 

创建.vscode 文件夹

在里面添加c_cpp_properties.json和settings.json文件。

在生成的c_cpp_properties.json文件中添加Linux源码文件夹

 在"${workspaceFolder}/**"下添加如下目录

"/home/zzk/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", 
"/home/zzk/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", 
"/home/zzk/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"

 在setting.json中过滤掉一些不用看的文件

{
    "search.exclude": {
        "**/node_modules": true,
        "**/bower_components": true,
        "**/*.o":true,
        "**/*.su":true, 
        "**/*.cmd":true,
        "Documentation":true,      
    },
    "files.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/CVS": true,
        "**/.DS_Store": true,  
        "**/*.o":true,
        "**/*.su":true, 
        "**/*.cmd":true,
        "Documentation":true, 
    }
}

 2编写beep.c文件

添加头文件
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define BEEP_CNT			1		/* 设备号个数 */
#define BEEP_NAME			"beep"	/* 名字 */
#define BEEPOFF 			0		/* 关蜂鸣器 */
#define BEEPON 				1		/* 开蜂鸣器 */

/* beep设备结构体 */
struct beep_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	int beep_gpio;			/* beep所使用的GPIO编号		*/
};

struct beep_dev beep;		/* beep设备 */

 设置设备号和名字,还有一些常用的宏定义

beep设备结构体

存放beep所必须的一些变量

    dev_t devid;            /* 设备号      */

        dev_t是u32的重定义 上面等价于 u32 devid 。devid用来存储设备号 ,大小为32位

    struct cdev cdev;        /* cdev     */

        cdev 是一个管理字符设备。使用时在入口函数先用cdev_init初始化函数,初始化后在用cdev_add 向linux添加设备。卸载驱动时候用cdev_del 删除函数。

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

在 cdev 中有两个重要的成员变量:ops 和 dev,这两个就是字符设备文件操作函数集合 file_operations 以及设备号 dev_t。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个 变量就表示一个字符设备,如下所示: struct beep_dev beep;

cdev_init 函数

定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化,cdev_init 函数原型如下:        void cdev_init(struct cdev *cdev, const struct file_operations *fops)

	/* 2、初始化cdev */
	beep.cdev.owner = THIS_MODULE;
	cdev_init(&beep.cdev, &beep_fops);

 cdev_add 函数

cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。 cdev_add 函数原型如下: int cdev_add(struct cdev *p, dev_t dev, unsigned count)

    /* 3、添加一个cdev */
	cdev_add(&beep.cdev, beep.devid, BEEP_CNT);

 3、cdev_del 函数

卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备,cdev_del 函数原型如下: void cdev_del(struct cdev *p)

    /* 注销字符设备驱动 */
	cdev_del(&beep.cdev);/*  删除cdev */
    struct class *class;    /* 类 */  struct device *device;    /* 设备      */

        类和设备的作用是用来自动创建设备节点

使用时候在入口函数cdev_add 函数后用class_create创建类,然后在用device_create函数创建设备。卸载驱动时候在exit函数下要用device_destroy,class_destroy删除掉类和设备

/* 4、创建类 */
	beep.class = class_create(THIS_MODULE, BEEP_NAME);
	if (IS_ERR(beep.class)) {
		return PTR_ERR(beep.class);
	}

	/* 5、创建设备 */
	beep.device = device_create(beep.class, NULL, beep.devid, NULL, BEEP_NAME);
	if (IS_ERR(beep.device)) {
		return PTR_ERR(beep.device);
	}
	device_destroy(beep.class, beep.devid);
	class_destroy(beep.class);

    int major;                /* 主设备号      */

 设备号的高12位

    int minor;                /* 次设备号   */

  设备号的低20位


    struct device_node    *nd; /* 设备节点 */

用来存储设备树中的节点, 如下所示

使用方法 

beep.nd = of_find_node_by_path("/beep");

 通过of_find_node_by_path就可以找到设备树中的节点。通过of系列函数,可以找到在设备树中保存的信息,比如名字寄存器等,通过这些信息就可以对开饭版上的资源进行配置和使用。


    int beep_gpio;            /* beep所使用的GPIO编号        */

   可以通过下面方法来获取gpio编号。

	beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
 设备操作函数

Linux中,设备驱动程序需要实现一组标准的函数,以便操作系统能够识别和操作设备。下面是一部分常用的操作函数。即读写和开关函数。

/* 设备操作函数 */
static struct file_operations beep_fops = {
    .owner = THIS_MODULE,
    .read = beep_read,
    .open = beep_open,
    .write = beep_write,
    .release =     beep_release,
};

 因为beep只需要控制,不需要读取信息,这里就没有写,

open函数里面就做了一个工作,将数据设置为私有数据,这是为了保证beep中的数据不会被其他设备改变。

重点讲下write函数,下面write函数实现的功能就是把用户在Linux 命令行中向beep写入的数据解析

并作出相应的操作。

如 ./beepApp /dev/beep 1 就是运行beepAPP 向beep中写如数字1

只需要看beepAPP中的一部分

/* 向/dev/beep文件写入数据 */                                                                                                retvalue = write(fd, databuf, sizeof(databuf));

fd就是指beep这个设备 ,databuf就是写入的数字1                                                                            在beep_write函数中通过copy_from_user将数字接受,在根据是1来开启蜂鸣器。

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int beep_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &beep; /* 设置私有数据 */
	return 0;
}
/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char beepstat;
	struct beep_dev *dev = filp->private_data;
	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}
	beepstat = databuf[0];		/* 获取状态值 */
	if(beepstat == BEEPON) {	
		gpio_set_value(dev->beep_gpio, 0);	/* 打开蜂鸣器 */
	} else if(beepstat == BEEPOFF) {
		gpio_set_value(dev->beep_gpio, 1);	/* 关闭蜂鸣器 */
	}
	return 0;
}
/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int beep_release(struct inode *inode, struct file *filp)
{
	return 0;
}
 驱动入口和出口函数

驱动入口函数是在函数注册时候所执行的函数,用来设备号的设定,节点注册等。                       驱动出口函数是在驱动卸载时候运行的,用来清除设备号和设备节点等。

用下面函数来指定驱动的出口和入口函数。                       module_init(beep_init);module_exit(beep_exit);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值