一:修改设备树文件
iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10B0 /* LED1 */
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x10B0 /* LED2 */
MX6UL_PAD_GPIO1_IO08__GPIO1_IO08 0x10B0 /* LED3 */
>;
};
二:添加 LED 设备节点
在根节点“/”下创建 LED 灯节点,节点名为“gpioled”
gpioled{
#address-cells = <1>;
#size-cells = <1>;
compatible = "fireMini,gpioled";
pinctrl-name = "default";
pinctrl-0 = <&pinctrl_led>;
led1-gpio = <&gpio1 4 GPIO_ACTIVE_LOW>;
led2-gpio = <&gpio1 9 GPIO_ACTIVE_LOW>;
led3-gpio = <&gpio1 8 GPIO_ACTIVE_LOW>;
status = "okay";
}
(1)pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点
(2)led1-gpio 属性指定了 LED 灯所使用的 GPIO,在这里就是 GPIO1 的 IO04,低电平
有效。稍后编写驱动程序的时候会获取 led-gpio 属性的内容来得到 GPIO 编号,因为 gpio 子系
统的 API 操作函数需要 GPIO 编号。led2-gpio,led3-gpio类似
三:编写驱动程序
1.驱动的入口和出口函数
我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:
module_init(xxx_init); module_exit(xxx_exit); |
//注册模块加载函数 //注册模块卸载函数 |
module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的
具体函数,当使用“insmod”命令加载驱动的时候, xxx_init 这个函数就会被调用。 module_exit()
函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使
用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。字符设备驱动模块加载和卸
载模板如下所示:
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 出口函数具体内容 */
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);
2.注册字符设备驱动
对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模
块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示:
设备号分配方式有两种,一种是主动设置设备号,另外一种是动态分配设备号,在实际开发中主要是动态分配
/* 1.注册字符设备驱动 */
gpioled.major = 0;
if(gpioled.major){
gpioled.devid = MKDEV(gpioled.major,0);
register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME);
}else{
alloc_chrdev_region(&gpioled.devid,0,GPIOLED_CNT,GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
3.初始化cdev
在 Linux 中使用 cdev 结构体表示一个字符设备, cdev 结构体在 include/linux/cdev.h 文件中
的定义如下:
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 结构体变量,这个
变量就表示一个字符设备。
定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化, cdev_init 函数原型如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数
完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。
cdev_add 函数原型如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备, cdev_del
函数原型如下:
void cdev_del(struct cdev *p)
参数 p 就是要删除的字符设备。如果要删除字符设备,参考如下代码:
示例代码 42.1.2.3 cdev_del 函数使用示例
1 cdev_del(&testcdev); /* 删除 cdev */
cdev_del 和 unregister_chrdev_region 这两个函数合起来的功能相当于 unregister_chrdev 函
数
6.创建类class
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添
加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件
include/linux/device.h 里面。 class_create 是类创建函数, class_create 是个宏定义,内容如下:
1 #define class_create(owner, name) \
2 ({ \
3 static struct lock_class_key __key; \
4 __class_create(owner, name, &__key); \
5 })
6 7
struct class *__class_create(struct module *owner, const char *name,
8 struct lock_class_key *key
根据上述代码,将宏 class_create 展开以后内容如下:
struct class *class_create (struct module *owner, const char *name)
class_create 一共有两个参数,参数 owner 一般为 T