i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED

Posted Mculover666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED相关的知识,希望对你有一定的参考价值。

前置知识

一、编写基本设备驱动模块

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>

dev_t led_dts_dev;
struct cdev *led_dts_cdev;
struct class *led_dts_class;
struct device *led_dts_device0;

static int led_dts_open(struct inode *inode, struct file *fp)

    return 0;


static int led_dts_read(struct file *fp, char __user *buf, size_t size, loff_t *off)

    return 0;


static int led_dts_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)

    return 0;


static int led_dts_release(struct inode *inode, struct file *fp)

    return 0;


static struct file_operations led_dts_fops = 
    .owner = THIS_MODULE,
    .open = led_dts_open,
    .read = led_dts_read,
    .write = led_dts_write,
    .release = led_dts_release,
;

static int __init led_dts_init(void)

    int ret;

    //分配cdev设备号
    ret = alloc_chrdev_region(&led_dts_dev, 0, 1, "led_dts");
    if (ret != 0) 
        printk("alloc_chrdev_region fail!");
        return -1;
    

    //初始化cdev
    led_dts_cdev = cdev_alloc();
    if (!led_dts_cdev) 
        printk("cdev_alloc fail!");
        return -1;
    

    //设置fop操作函数
    led_dts_cdev->owner = THIS_MODULE;
    led_dts_cdev->ops = &led_dts_fops;

    //注册cdev
    cdev_add(led_dts_cdev, led_dts_dev, 1);

    // 创建设备类
    led_dts_class = class_create(THIS_MODULE, "led_dts_class");
    if (!led_dts_class) 
        printk("class_create fail!");
        return -1;
    

    //创建设备节点
    led_dts_device0 = device_create(led_dts_class, NULL, led_dts_dev, NULL, "led0");
    if (IS_ERR(led_dts_device0)) 
        printk("device_create led_dts_device0 fail!");
        return -1;
    

    return 0;


static void __exit led_dts_exit(void)

    // 将设备从内核删除
    cdev_del(led_dts_cdev);

    // 释放设备号
    unregister_chrdev_region(led_dts_dev, 1);

    // 删除设备节点
    device_destroy(led_dts_class, led_dts_dev);

    // 删除设备类
    class_destroy(led_dts_class);


module_init(led_dts_init);
module_exit(led_dts_exit);

MODULE_AUTHOR("Mculover666");
MODULE_LICENSE("GPL");

编译脚本:

KERNEL_DIR = /home/mculover666/imx6ull/kernel/linux-imx6ull
obj-m := led_dts.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules

clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

编译:

make

二、修改设备树文件

正点原子i.MX6ULL开发板上的LED连接到 GPIO1_IO03 引脚。

添加设备树自定义描述文件

添加自定义设备树描述文件 arch/arm/boot/dts/imx6ull-atk-emmc-custom.dtsi,以后自定义的节点添加都在该文件中进行。

在设备树描述文件arch/arm/boot/dts/imx6ull-atk-emmc.dts中引用我们的自定义文件:

#include "imx6ull-atk-emmc-custom.dtsi"

1. 添加pinctrl子节点

在 iomuxc 节点的 imx6ul-evk 子节点下,新添加一个名为 pinctrl_led0的节点,用来描述引脚复用。

pinctrl_led0: led0grp 
	fsl,pins = <
		MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x10B0
	>;
;

2. 添加led0设备节点

在根节点下添加led0节点,节点名为led0,如下。

led0 
	compatible = "gpio-led";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_led0>;
	led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
	status = "okay";
;

3. 检查pin是否已经被使用

(1)全局搜索pinctrl占用MX6UL_PAD_GPIO1_IO03

找到,已经被占用为电阻触摸屏(TSC)外设的pin。

直接删除节点 pinctrl_tsc。

(2)全局搜索gpio占用gpio1 3

删除该段对于tsc节点的补充描述。

4. 重新编译设备树

进入内核目录:

make dtbs


使用新的设备树文件启动内核,查看是否有新添加的led0子节点。

三、编写led gpio硬件初始化函数

主要完成:

  • 子系统从设备树中获取led所使用的gpio
  • 初始化gpio

包含头文件:

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>

定义全局变量:

struct device_node *node;
int led_gpio;

编写硬件初始化函数:

static int board_led_init(void)

    int ret;

    /* 设置LED所使用的GPIO */
    
    //在设备树寻找节点
    node = of_find_node_by_path("/led0");
    if (!node) 
        printk("of_find_node_by_path fail!");
        return -1;
    

    //获取gpio属性值,得到LED使用的gpio编号
    led_gpio = of_get_named_gpio(node, "led-gpio", 0);
    if (led_gpio < 0) 
        printk("of_get_named_gpio fail!");
        return -2;
    
    printk("led-gpio num is %d", led_gpio);

    //设置gpio方向并输出默认电平
    ret = gpio_direction_output(led_gpio, 1);
    if (ret < 0) 
        printk("gpio_direction_output fail!");
        return -3;
    

    return 0;

在驱动加载函数最开始调用:

// led硬件初始化
 ret = board_led_init();
 if (ret < 0) 
     printk("board_led_init fail, err is %d", ret);
     return -1;
 

四、全局变量封装

一路写下来,整个驱动模块的全局变量非常多,用一个结构体来进行管理:

struct gpioled_dev 
    dev_t led_dts_dev;                  /*!< 设备号 */
    struct cdev *led_dts_cdev;          /*!< cdev对象 */
    struct class *led_dts_class;        /*!< 设备类  */
    struct device *led_dts_device0;     /*!< 设备节点 */

    struct device_node *node;           /*!< 设备树节点  */
    int led_gpio;                       /*!< led使用的gpio编号  */
;

static struct gpioled_dev gpioled;

相关变量的使用也需要同步修改。

五、LED对应驱动

1. open

static int led_dts_open(struct inode *inode, struct file *fp)

    /* 设置私有数据 */
    fp->private_data = &gpioled;

    return 0;

2. read

static int led_dts_read(struct file *fp, char __user *buf, size_t size, loff_t *off)

    return 0;

3. write

static int led_dts_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)

    int ret;
    unsigned char data_buf[1];
    unsigned char led_status;
    struct gpioled_dev *dev = fp->private_data;

    // 拷贝用户传入数据
    ret = copy_from_user(data_buf, buf, 1);
    if (ret < 0) 
        printk("led write failed!\\n");
        return -EFAULT;
    

    // 控制LED
    led_status = data_buf[0];
    if (led_status == 0) 
        // 关闭LED,高电平关闭
        gpio_set_value(dev->led_gpio, 1);
     else if (led_status == 1)
        // 打开LED,低电平打开
        gpio_set_value(dev->led_gpio, 0);
    

    return 0;

4. release

static int led_dts_release(struct inode *inode, struct file *fp)

    return 0;

至此,驱动编写完成,编译:

make

六、驱动测试

1. 加载驱动模块

insmod led_dts.ko


查看是否加载成功:

查看是否注册了设备类:

查看是否有设备节点:

2. 编写测试程序

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>

int led_ctrl(char *filename, int status)

    int fd;
    int ret;
    unsigned char data_buf[1];

    if (!filename) 
        return -1;
    

    // 打开设备文件
    fd = open(filename, O_RDWR);
    if (fd < 0) 
        printf("open %s error!\\n", filename);
        return 0;
    

    // 写文件
    data_buf[0] = status;

    ret = write(fd, data_buf, sizeof(data_buf));
    if (ret < 0) 
        printf("write %s error!\\n", data_buf);
    

    // 关闭文件
    close(fd);

    return 0;


int main(int argc, char *argv[])

    uint32_t interval;

    // 检查参数
    if (argc != 3) 
        printf("usage: ./test_led_blink [device] [led blink interval(s)]\\n");
        return -1;
    

    interval = atoi(argv[2]);
    if (interval < 1) 
        interval = 1;
    

    while (1) 
        led_ctrl(argv[1], 1);
        sleep(interval);

        led_ctrl(argv[1], 0);
        sleep(interval);
    

编译:

arm-linux-gnueabihf-gcc test_led_blink.c -o test_led_blink

3. 测试led闪烁

在开发板上执行:

./test_led_blink /dev/led0 1

可以看到LED闪烁了起来,大功告成。

以上是关于i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED的主要内容,如果未能解决你的问题,请参考以下文章

i.MX6ULL驱动开发 | 07 -gpio子系统

i.MX6ULL驱动开发 | 10 - 修改LCD驱动点亮LCD显示小企鹅logo

i.MX6ULL驱动开发 | 17 - Linux中断机制及使用方法(taskletworkqueue软中断)

i.MX6ULL驱动开发 | 27 - 使用WM8960 CODEC播放音频

i.MX6ULL驱动开发 | 27 - 使用WM8960 CODEC播放音频

i.MX6ULL驱动开发 | 04-Linux设备树基本语法与实例解析