linux驅動筆記(一):多路led燈驅動——基於4.X內核

  (一)設備樹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");

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章