【Linux 驱动基础】IMX6ULL LED基础驱动

本机使用的是正点原子的IMX6ULL开发板

# 前置知识

IMX6ULL GPIO控制框图:

GPIO控制代码大概分为几个流程:开启时钟、设置IO复用、设置IO属性、配置IO方向、设置IO输出电平,下面以IMX6ULL为例,

1. 开启时钟

参考资料:芯片手册《Chapter 18: Clock Controller Module (CCM)》

关于CCM_CCGRx主要是由如下介绍:

00:该 GPIO 模块全程被关闭

01:该 GPIO 模块在 CPU run mode 情况下是使能的;在 WAIT 或 STOP模式下,关闭

10:保留

**11:**该 GPIO 模块全程使能

IMX6ULL 使用的LED引脚为GPIO1_IO03,GPIO1时钟控制:

2. 设置IO复用

参考资料:芯片手册《Chapter 32: IOMUX Controller (IOMUXC)》

选择功能:

a) IOMUXC_SW_MUX_CTL_PAD_<PADNAME> : Mux pad xxx,选择某个 pad 的功能

b) IOMUXC_SW_MUX_CTL_GRP_<GROUP NAME>: Mux grp xxx,选择某组引脚的功能

比如我们需要选择的LED灯引脚为GPIO1_IO03,那么选择对应的寄存器,设置复用模式为ALT5即可:

3. 设置IO属性

参考资料:芯片手册《Chapter 32: IOMUX Controller (IOMUXC)》

设置IO属性等参数:

a) IOMUXC_SW_PAD_CTL_PAD_<PAD_NAME>: pad pad xxx,设置某个 pad 的参数

b) IOMUXC_SW_PAD_CTL_GRP_<GROUP NAME>: pad grp xxx,设置某组引脚的参数

比如我们需要选择的LED灯引脚为GPIO1_IO03,那么选择对应的寄存器:

4. 设置IO方向

参考资料:芯片手册《Chapter Chapter 28 General Purpose Input/Output (GPIO)》

GPIO的框图如下:

GPIO模块一共有8个寄存器:

  • 设置方向寄存器:GPIO_GDIR
  • 设置输出电平寄存器:GPIO_DR
  • 读取输入电平寄存器:GPIO_PSR
  • 中断控制寄存器:GPIO_ICR1, GPIO_ICR2
  • 边沿选择寄存器:GPIO_EDGE_SEL
  • 中断掩码寄存器:GPIO_IMR
  • 中断状态寄存器:GPIO_ISR

我们需要控制LED灯打开或者关闭,那么就需要以下步骤:

  • 设置GPIO为输出
  • 控制GPIO输出

# 驱动程序

1. 驱动代码

cpp 复制代码
#include <linux/module.h>
#include <linux/major.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/delay.h>	/* guess what */
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/firmware.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>	/* For put_user and get_user */

#include "atk_led_op.h"

struct pri_led_TypeDef
{
    char drv_name[50];  /* 驱动名称 */

    int major;      /* 主设备号 */
    int minor;      /* 次设备号 */
    dev_t devt;     /* 设备号 */

    struct device *device;   /* 设备 */
    char device_name[50];    /* 设备名称 */

    struct class *class;    /* 类 */
    char class_name[50];    /* 类名称 */
};

static struct pri_led_TypeDef pri_led = {
    .drv_name = "led_drv",
    .major  = 0,
    .minor  = 0,
    .devt   = 0,
    .device = NULL,
    .device_name = "led_dev",
    .class  = NULL,
    .class_name  = "led_class",
};

struct led_operations *atk_led;

static int led_open(struct inode *inode, struct file *file)
{
    atk_led->init();
    return 0;
}

static int led_release(struct inode *inode, struct file *file)
{
    atk_led->deInit();
    return 0;
}

static ssize_t led_write(struct file *file, const char __user *buff, size_t size, loff_t *ppos)
{
    int err;
    unsigned char data[1];

    err = copy_from_user(data, buff, 1);

    if(data[0] == 1)
    {
        atk_led->ctrl(1);
    }
    else
    {
        atk_led->ctrl(0);
    }

    return 1;
}

static const struct file_operations led_op = {
    .owner      = THIS_MODULE,
    .open       = led_open,
    .release    = led_release,
    .write      = led_write,
};

static int __init led_init(void)
{
    int err;
    printk("led_init\r\n");
    pri_led.major = register_chrdev(0, pri_led.drv_name, &led_op);
    pri_led.devt = MKDEV(pri_led.major, pri_led.minor);

    pri_led.class = class_create(THIS_MODULE, pri_led.class_name);
    if(IS_ERR(pri_led.class))
    {
        printk("class_create error\r\n");
        err = PTR_ERR(pri_led.class);
        goto err_class_create_out;
    }

    pri_led.device = device_create(pri_led.class, NULL, pri_led.devt, NULL, pri_led.device_name);
    if(IS_ERR(pri_led.device))
    {
        printk("device_create error\r\n");
        err = PTR_ERR(pri_led.device);
        goto err_device_create_out;
    }

    atk_led = atk_led_op_register();

    return 0;

err_device_create_out:
    class_destroy(pri_led.class);
err_class_create_out:
    unregister_chrdev(pri_led.major, pri_led.drv_name);
    return err;
} 

static void __exit led_exit(void)
{
    printk("led_exit\r\n");
    device_destroy(pri_led.class, pri_led.devt);
    class_destroy(pri_led.class);
    unregister_chrdev(pri_led.major, pri_led.drv_name);
}


module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

2. 板级驱动代码

cpp 复制代码
#ifndef __ATK_LED_OP_H__
#define __ATK_LED_OP_H__

#include <linux/io.h>
#include <linux/module.h>
#include <linux/major.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/delay.h>	/* guess what */
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/firmware.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>	/* For put_user and get_user */

struct led_operations
{
    void (*init)(void);
    void (*deInit)(void);
    int (*ctrl)(int status);
};

struct led_operations *atk_led_op_register(void);

#endif /* __ATK_LED_CTRL_H__ */
cpp 复制代码
#include "atk_led_op.h"

/**
 * 1. 查看原理图,LED0 --> GPIO1_IO03
 * 2. 开启时钟,CCM_CCGR1->CG13     //Address: 20C_4000h base + 6Ch offset = 20C_406Ch
 * 3. 设置IO复用,IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03      //Address: 20E_0000h base + 68h offset = 20E_0068h
 * 4. 设置IO属性,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03      //Address: 20E_0000h base + 2F4h offset = 20E_02F4h
 * 5. GPIO1寄存器:  GPIO1_DR           //Address: 209_C000
 *                  GPIO1_GDIR          //Address: 209_C004
 *                  GPIO1_PSR           //Address: 209_C008
 *                  GPIO1_ICR1          //Address: 209_C00C
 *                  GPIO1_ICR2          //Address: 209_C010
 *                  GPIO1_IMR           //Address: 209_C014
 *                  GPIO1_ISR           //Address: 209_C018
 *                  GPIO1_EDGE_SEL      //Address: 209_C01C             
*/

#define CCM_CCGR1_ADDR                              0x20C406C
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_ADDR       0x20E0068
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_ADDR       0x20E02F4
#define GPIO1_BASE_ADDR                             0x209C000           

typedef struct
{
    volatile uint32_t GPIO_DR;
    volatile uint32_t GPIO_GDIR;
    volatile uint32_t GPIO_PSR;
    volatile uint32_t GPIO_ICR1;
    volatile uint32_t GPIO_ICR2;
    volatile uint32_t GPIO_IMR;
    volatile uint32_t GPIO_ISR;
    volatile uint32_t GPIO_EDGE_SEL;
}GPIO_TypeDef;

static GPIO_TypeDef *GPIO1;

static volatile uint32_t *CCM_CCGR1;
static volatile uint32_t *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
static volatile uint32_t *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03;

void atk_led_init(void)
{
    uint32_t val;

    /*1. 地址映射 */
    GPIO1 = ioremap(GPIO1_BASE_ADDR, sizeof(GPIO_TypeDef));
    CCM_CCGR1 = ioremap(CCM_CCGR1_ADDR, 4);
    IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_ADDR, 4);
    IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = ioremap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_ADDR, 4);

    /* 2. 使能GPIO1时钟 */
    *CCM_CCGR1 |= (3 << 26);

    /* 3. 设置IO复用 */
    val = *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
    val &= ~(0x0F);
    val |= 5;
    *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = val;

    /* 4. 设置IO属性 */

    /* 5. 设置IO方向,设置GPIO1_IO03为输出 */
    GPIO1->GPIO_GDIR |= (1 << 3);
}

void atk_led_deInit(void)
{
    /* 取消地址映射 */
    iounmap(GPIO1);
    iounmap(CCM_CCGR1);
    iounmap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03);
    iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);
}

int atk_led_ctrl(int status)
{
    if(status == 1)
    {
        GPIO1->GPIO_DR &= ~(1<<3);
    }
    else
    {
        GPIO1->GPIO_DR |= (1<<3);
    }

    return 1;
}

static struct led_operations led_op = {
    .init   = atk_led_init,
    .deInit = atk_led_deInit,
    .ctrl   = atk_led_ctrl,
};

struct led_operations *atk_led_op_register(void)
{
    return &led_op;
}

3. 测试APP

cpp 复制代码
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/*
 * ./test_app  /dev/test_drv on/off
 */
int main(int argc, char **argv)
{
    int fd;
    unsigned char buff[1];
    int len;

    /* 1. 判断输入参数 */
    if(argc < 3)
    {
		printf("Usage: %s on/off\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

    /* 2. 打开文件 */
    fd = open(argv[1], O_RDWR);
    if(fd == -1)
    {
        printf("can not open file %s\n", argv[1]);
		return -1;
    }

    /* 3. 读写文件 */
    if((strcmp(argv[2], "on") == 0))
    {
        buff[0] = 1;
        write(fd, buff, 1);
    }
	else
	{
        buff[0] = 0;
		write(fd, buff, 1);
	}

    close(fd);

    return 0;
}
相关推荐
学Linux的语莫3 分钟前
Ansible使用简介和基础使用
linux·运维·服务器·nginx·云计算·ansible
踏雪Vernon13 分钟前
[OpenHarmony5.0][Docker][环境]OpenHarmony5.0 Docker编译环境镜像下载以及使用方式
linux·docker·容器·harmonyos
学Linux的语莫26 分钟前
搭建服务器VPN,Linux客户端连接WireGuard,Windows客户端连接WireGuard
linux·运维·服务器
legend_jz31 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
Komorebi.py32 分钟前
【Linux】-学习笔记04
linux·笔记·学习
黑牛先生33 分钟前
【Linux】进程-PCB
linux·运维·服务器
友友马1 小时前
『 Linux 』网络层 - IP协议(一)
linux·网络·tcp/ip
wenchm1 小时前
细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的另一种方法
stm32·单片机·嵌入式硬件
猿java2 小时前
Linux Shell和Shell脚本详解!
java·linux·shell
电子工程师UP学堂2 小时前
电子应用设计方案-16:智能闹钟系统方案设计
单片机·嵌入式硬件