Bootstrap

嵌入式LINUX驱动开发(三)-设备树驱动led

 1)添加pinctrl节点

使用开发板自带的LED灯进行操作,打开dts文件,在iomuxc_snvs下创建一个“pinctrl_led的子节点”

```
pinctrl_leds: ledgrp {
    fsl,pins = <
          MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0X10B0
              >;
         };
```

2)添加LED设备节点

在根节点"/"创建LED灯节点,节点名称为**"gpioled"**,节点内容如下:

```
     gpioled {
         #address-cells = <1>;
         #size-cells = <1>;
         compatible = "100ask-gpioled";
         pinctrl-names = "default";
         pinctrl-0 = <&pinctrl_leds>;
         led-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>;
         status = "okay";
     };

```

3)检查PIN是否被其他外设使用

检查 PIN 有没有被其他外设使用包括两个方面:

①、检查 pinctrl 设置(看是否有其他的使用这个,MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03,有使用的可以注释掉)。

②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。(同时把使用这个重复的也注释掉)

 4)编译dtb

 5)编写驱动程序gpioled.c

```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 GPIOLED_CNT     1
#define GPIOLED_NAME    "gpioled"
#define LEDOFF          0
#define LEDON           1

// gpioled设备结构体
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;

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;
}

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;  // 修正:file -> filp

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0) {
        printk("kernel write failed!\r\n");  // 修正:kernal -> kernel
        return -EFAULT;
    }

    ledstat = databuf[0];

    if (ledstat == LEDON) {  // 修正:ledtest -> ledstat
        // 打开led灯
        gpio_set_value(dev->led_gpio, 0);
    } else if (ledstat == LEDOFF) {
        // 关闭led灯
        gpio_set_value(dev->led_gpio, 1);
    }
    return cnt;  // 返回实际写入的字节数
}

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,
};

// 驱动入口函数
static int __init led_init(void)
{
    int ret = 0;

    // 设置led所使用的gpio
    // 1. 获取设备节点:gpioled
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL) {
        printk("gpioled node can not found\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node has been found\r\n");
    }

    // 2. 获取设备树中的gpio属性,得到led所使用的led编号
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if (gpioled.led_gpio < 0) {
        printk("can not get led-gpio\r\n");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

    // 3. 设置GPIO5_IO03为输出,并且输出高电平,默认关闭led灯
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret < 0) {
        printk("can not set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1. 创建设备号 */
    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);
    }
    printk("gpioled major = %d, minor = %d\r\n", gpioled.major, gpioled.minor);

    // 2. 初始化cdev
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    // 3. 添加一个cdev
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    // 4. 创建类
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
        return PTR_ERR(gpioled.class);
    }
    // 5. 创建设备
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}

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);
}

module_init(led_init);  // 修正:module -> module_init
module_exit(led_exit);  // 修正:module -> module_exit
MODULE_LICENSE("GPL");
```
 6)编写测试程序led_test.c

```C++
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>

#define LEDOFF  0
#define LEDON   1

 int main(int argc,char ** argv)
 {
    int fd,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 == -1)
    {
       printf("can not open file %s\n",argv[1]);
       return -1;
    }

    databuf[0] = atoi(argv[2]);

    retvalue = write(fd,databuf,sizeof(databuf));
    if(retvalue == -1){
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd);
    if(retvalue < 0){
        printf("close %s failed\r\n",argv[1]);
        return -1;
    }
    return 0;
 }
```

6)编写测试程序led_test.c

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>

#define LEDOFF  0
#define LEDON   1

 int main(int argc,char ** argv)
 {
    int fd,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 == -1)
    {
       printf("can not open file %s\n",argv[1]);
       return -1; 
    }

    databuf[0] = atoi(argv[2]);

    retvalue = write(fd,databuf,sizeof(databuf));
    if(retvalue == -1){
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd);
    if(retvalue < 0){
        printf("close %s failed\r\n",argv[1]);
        return -1;
    }
    return 0;
 }

7)写makefile文件

```
KERN_DIR =  /home/xxl/100ask_imx6ull_mini-sdk/Linux-4.9.88 # 板子所用内核源码的目录
all:
    make -C $(KERN_DIR) M=`pwd` modules
    $(CROSS_COMPILE)gcc -o led_test led_test.c
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order  led_test

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += gpioled.o
```

 8)总结

配置电气属性

配置设备

编写makefile

make编译得到ko文件和可执行文件

移植到开发板

insmod操作,将.ko文件注册进入设备

可以通过lsmod查看是否注册成功,同时也可以切换到 cd /dev  设备是否注册

然后就可以开始运行,示例:./ledtest /dev/gpioled 1led_test

;