linux驱动笔记(二):多路led灯驱动——基于4.X内核

本文详细介绍了一种基于设备树DTS配置和GPIO接口的LED驱动实现方法,通过在DTS中定义LED节点并设置相应的GPIO属性,实现了对两个LED灯(其中一个由蜂鸣器模拟)的控制。同时,提供了完整的驱动代码示例,展示了如何读取DTS配置、初始化GPIO,并响应用户空间的读写请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  (一)设备树dts

红色加粗为修改增加部分,由于板子上只有一个LED灯,所以用蜂鸣器模拟另外一路led灯。这里采用在根节点下创建一个leds节点下,然后在leds节点新建2路子节点   led1 和  beep  来实现。(也可以在根节点下单独建立2个节点方式来实现)

/{    

leds {
        compatible = "fsl,gpio-led-test";
        #address-cells = <1>;
        #size-cells = <1>;
        pinctrl-names = "default";
        status = "okay";
        led1 {
            compatible = "fsl,led1-test";
            pinctrl-0 = <&pinctrl_led>;
            gpios-led = <&gpio1 3 GPIO_ACTIVE_LOW>;
            status = "okay";
        };
        
        beep {
            compatible = "fsl,beep-test";
            pinctrl-0 = <&pinctrl_beep>;
            gpios-beep = <&gpio5 1 GPIO_ACTIVE_LOW>;
            status = "okay";
        };
    };

.......

}

&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        pinctrl_hog_1: hoggrp-1 {
            fsl,pins = <
                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19    0x17059 /* SD1 CD */
                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT    0x17059 /* SD1 VSELECT */
                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x17059 /* SD1 RESET */
            >;
        };

        pinctrl_led: ledgrp {
            fsl,pins = <
                MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x10b0
            >;
        };
        pinctrl_beep: beepgrp {
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01        0x10b0
            >;
        };

.......

}

(二)驱动代码:

 

 

#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 2 /* 设备号个数 */
  #define GPIOLED_NAME "ledtest" /* 名字 */
  #define LEDOFF 0 /* 关灯 */
  #define LEDON 1 /* 开灯 */
  
  /* gpioled 设备结构体 */
  struct gpioled_dev{
      dev_t devid; /* 设备号 */
      struct cdev cdev; /* cdev */
      struct class *class; /* 类 */
      struct device *device[2]; /* 设备 */
      int major; /* 主设备号 */
      int minor; /* 次设备号 */
      struct device_node *nd[GPIOLED_CNT]; /* 设备节点 */
      int gpios[GPIOLED_CNT]; /* led 所使用的 GPIO 编号 */
  };
  
  struct gpioled_dev gpioled; /* led 设备 */
  
  /*
   * @description : 打开设备
   * @param – inode : 传递给驱动的 inode
   * @param – filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
   * 一般在 open 的时候将 private_data 指向设备结构体。
   * @return : 0 成功;其他 失败
   */
  static int led_open(struct inode *inode, struct file *filp)
  {
    int minor = MINOR(inode->i_rdev);
      filp->private_data = &gpioled; /* 设置私有数据 */
      printk("open minor is %d\r\n",minor);

      return 0;
  }

  
  /*
   * @description : 从设备读取数据
   * @param – filp : 要打开的设备文件(文件描述符)
   * @param - buf : 返回给用户空间的数据缓冲区
   * @param - cnt : 要读取的数据长度
   * @param – offt : 相对于文件首地址的偏移
   * @return : 读取的字节数,如果为负值,表示读取失败
   */
  static ssize_t led_read(struct file *filp, char __user *buf,
          size_t cnt, loff_t *offt)
  {
      return 0;
  }
  
  /*
   * @description : 向设备写数据
   * @param - filp : 设备文件,表示打开的文件描述符
   * @param - buf : 要写给设备写入的数据
   * @param - cnt : 要写入的数据长度
   * @param – offt : 相对于文件首地址的偏移
   * @return : 写入的字节数,如果为负值,表示写入失败
   */
  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;

    int minor = MINOR(file_inode(filp)->i_rdev);/*获取设备的子节点号,在4.x内核必须用file_inode(filp)代替filp->f_dentry->inode,不然编译会出错*/

      printk("write inode minor is %d\r\n",minor);

      retvalue = copy_from_user(databuf, buf, cnt);
      if(retvalue < 0){
          printk("kernel write failed!\r\n");
          return -EFAULT;
      }
  
      ledstat = databuf[0]; /* 获取状态值 */
    switch(minor){
        case 0:
                  if(ledstat == LEDON) {
                      gpio_set_value(dev->gpios[0], 0); /* 打开 LED 灯 */
                  } else if(ledstat == LEDOFF) {
                      gpio_set_value(dev->gpios[0], 1); /* 关闭 LED 灯 */
                  }
        break;
        case 1:
                  if(ledstat == LEDON) {
                      gpio_set_value(dev->gpios[1], 1); /* 打开 LED 灯 */
                  } else if(ledstat == LEDOFF) {
                      gpio_set_value(dev->gpios[1], 0); /* 关闭 LED 灯 */
                  }
        break;
        default:
        break;

    }

      return 0;
  }
  
  /*
   * @description : 关闭/释放设备
   * @param – filp : 要关闭的设备文件(文件描述符)
   * @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,
  };
  
  /*
   * @description : 驱动入口函数
   * @param : 无
   * @return : 无
   */
  static int __init led_init(void)
  {
      int ret = 0;
      struct property *proper;
      /* 设置 LED 所使用的 GPIO */
      /* 1、获取设备节点:gpioled */
      gpioled.nd[0] = of_find_node_by_path("/leds/led1");
      if(gpioled.nd[0] == NULL) {
          printk("gpioled node cant not found!\r\n");
          return -EINVAL;
      } else {
          printk("gpioled node has been found!\r\n");
      }
  
      /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
      gpioled.gpios[0] = of_get_named_gpio(gpioled.nd[0], "gpios-led", 0);
      if(gpioled.gpios[0] < 0) {
          printk("can't get led-gpio!\r\n");
          return -EINVAL;
      }
      printk("led-gpio num = %d\r\n", gpioled.gpios[0]);
  
      /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
      ret = gpio_direction_output(gpioled.gpios[0], 1);
      if(ret < 0) {
          printk("can't set le-gpio!\r\n");
      }
    /*获取字节点的compatible属性*/
      proper = of_find_property(gpioled.nd[0], "compatible", NULL);
    if(proper == NULL) {
        printk("compatible property find failed\r\n");
    } else {
        printk("led compatible = %s\r\n", (char*)proper->value);
    }


      /*  设置 BEEP 所使用的 GPIO */
        /* 1、获取设备节点:gpioled */
      gpioled.nd[1] = of_find_node_by_path("/leds/beep");
      if(gpioled.nd[1] == NULL) {
          printk("gpiobeep node cant not found!\r\n");
          return -EINVAL;
      } else {
          printk("gpiobeep node has been found!\r\n");
      }
  
      /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
      gpioled.gpios[1] = of_get_named_gpio(gpioled.nd[1], "gpios-beep", 0);
      if(gpioled.gpios[1] < 0) {
          printk("can't get beep-gpio!\r\n");
          return -EINVAL;
      }
      printk("beep-gpio num = %d\r\n", gpioled.gpios[1]);
  
      /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
      ret = gpio_direction_output(gpioled.gpios[1], 0);
      if(ret < 0) {
          printk("can't set beep-gpio!\r\n");
      }
    /*获取字节点的compatible属性*/
      proper = of_find_property(gpioled.nd[0], "compatible", NULL);
    if(proper == NULL) {
        printk("beep compatible property find failed\r\n");
    } else {
        printk("beep compatible = %s\r\n", (char*)proper->value);
    }


    /*符设备驱动 */
      /* 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 */
      ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if(ret)
    {
        printk("cdev_add erro!\r\n");
        goto cdevadd_erro;

    }
      printk("cdev_add ok!\r\n");
      /* 4、创建类 */
      gpioled.class = class_create(THIS_MODULE, "my-led");
      if (IS_ERR(gpioled.class)) {
        printk("class_create erro!\r\n");
          ret=PTR_ERR(gpioled.class);
        goto class_create_erro;

      }
  
      /* 5、创建设备 */
      gpioled.device[0] = device_create(gpioled.class, NULL,
              MKDEV(gpioled.major,0), NULL, "led1");
      if (IS_ERR(gpioled.device[0])) {
          ret =  PTR_ERR(gpioled.device[1]);
        goto device_create1_erro;
      }

      /* 5、创建设备 */
      gpioled.device[1] = device_create(gpioled.class, NULL,
              MKDEV(gpioled.major,1), NULL, "beep");
      if (IS_ERR(gpioled.device[1])) {
        
          ret = PTR_ERR(gpioled.device[1]);
      }
      return 0;
device_create1_erro:
      class_destroy(gpioled.class);
class_create_erro:
    cdev_del(&gpioled.cdev); /* 删除 cdev */
cdevadd_erro:
      unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); 
    return ret;
  }
  
  /*
     189 * @description : 驱动出口函数
   * @param : 无
   * @return : 无
   */
  static void __exit led_exit(void)
  {
      /* 注销字符设备驱动 */
      cdev_del(&gpioled.cdev); /* 删除 cdev */
      unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */
  
      device_destroy(gpioled.class, MKDEV(gpioled.major,0));
      device_destroy(gpioled.class, MKDEV(gpioled.major,1));
      class_destroy(gpioled.class);
  }
  
  module_init(led_init);
  module_exit(led_exit);
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("topeet");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小薛1988

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值