设备树下的字符设备驱动框架
(以驱动LED为例)
设备树下的字符设备驱动框架如上图所示,下面详细介绍代码的编写
1. 修改设备树文件
1.1 添加pinctrl节点
- 在iomuxc节点的imx6ul-evk子节点下创建“pinctrl_led”节点,复用GPIO1_IO03
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
1.2 添加LED设备节点
- 在根节点下创建LED设备节点,设置PIN对应的pinctrl节点,指定所使用的的GPIO
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "alpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
1.3 检查PIN是否冲突
- 检查pinctrl中设置以及设备节点中指定的引脚有没有被别的外设使用
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
>;
};
&tsc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
measure-delay-time = <0xffff>;
pre-charge-time = <0xfff>;
status = "okay";
};
1.4 编译设备树并启动Linux
- 使用“make dtbs”命令编译设备树,并使用该设备树启动Linux系统
make dtbs
cd /proc/device-tree
2. 编写驱动程序
2.1 定义设备结构体及设备
- 结构体中包含设备号/cdev/类/设备/主次设备号/设备节点/使用的GPIO编号
struct gpioled_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
};
struct gpioled_dev gpioled;
2.2 编写设备操作函数
static int led_open(struct inode *inode, struct file *filp){
filp->private_data = &gpioled;
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
return 0;
}
- led_write函数:向设备写数据,根据写入的值来操作GPIO
static ssize_t led_write(struct file *filp, 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);
}else if(ledstat == LEDOFF){
gpio_set_value(dev->led_gpio, 1);
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp){
return 0;
}
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
2.3 编写驱动入口函数
a. 获取设备节点
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
printk("gpioled node cant not found!\r\n");
return -EINVAL;
} else {
printk("gpioled node has been found!\r\n");
}
b. 获取gpio属性,得到所使用的GPIO编号
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio",0);
if(gpioled.led_gpio < 0) {
printk("cant not get led-gpio!\r\n");
return -EINVAL;
} else {
printk("led-gpio num = %d !\r\n",gpioled.led_gpio);
}
c. 设置GPIO为输出,并默认高电平
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0){
printk("can not set gpio!\r\n");
}
a. 创建设备号
if(gpioled.major){
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
}else{
alloc_chrdev_region(&gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
printk("gpioled major = %d, minor = %d\r\n",gpioled.major,gpioled.minor);
b. 初始化cdev
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
c. 添加cdev
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
d. 创建类
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
return PTR_ERR(gpioled.class);
}
e. 创建设备
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
return PTR_ERR(gpioled.device);
}
2.4 编写驱动出口函数
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);
}
2.5 其他
MODULE_LICENSE("GPL");
3. 编写测试APP
操作“/dev/led”驱动文件对LED进行控制
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
if(atoi(argv[2]) == 1)
retvalue = read(fd, readbuf, 50);
if(retvalue < 0){
printf("read file %s failed!\r\n", filename);
}else{
printf("read data:%s\r\n",readbuf);
}
}
if(atoi(argv[2]) == 2){
memcpy(writebuf, usrdata, sizeof(usrdata));
retvalue = write(fd, writebuf, 50);
if(retvalue < 0){
printf("write file %s failed!\r\n", filename);
}
}
retvalue = close(fd);
if(retvalue < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
4. 运行测试
4.1 编译驱动程序
obj-m := gpioled.o
make -j32
4.2 编译测试APP
- 使用“arm-linux-gnueabihf-gcc”命令编译测试APP
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
4.3 运行测试
- 将驱动文件和APP可执行文件拷贝至“rootfs/lib/modules/4.1.15”中
- 第一次加载驱动时,需使用“depmod”命令
depmod
modprobe gpioled.ko
- 使用“./ledApp /dev/gpioled 1"命令打开LED
./ledApp /dev/gpioled 1
- 使用“./ledApp /dev/gpioled 0"命令关闭LED
./ledApp /dev/gpioled 0
rmmod gpioled.ko
|