Linux驱动系列---2.led驱动开发

地址映射

内存管理单元(MMU)

主要功能:

  • 完成虚拟空间到物理空间的映射
  • 内存保护,设置存储器访问的权限,设置虚拟存储空间的缓冲特性。

在开启MMU之前,IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。直接向这个地址赋值就可以选择复用功能,但是开启MMU之后,必须获得这个寄存器的虚拟地址,操作虚拟地址才可以选择复用功能。物理地址和虚拟地址的转换需要用到:ioremap,iounmap。

ioremap

ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 ,定义在arch/arm/include/asm/io.h 文件中,定义如下:

#define ioremap(cookie,size) __arm_ioremap((cookie), (size),MT_DEVICE)

void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,unsigned int mtype)
{
	return arch_ioremap_caller(phys_addr, size, mtype,__builtin_return_address(0));
}
  • phys_addr:要映射给的物理起始地址。
  • size:要映射的内存空间大小。
  • mtype: ioremap 的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE。
  • 返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址。

iounmap

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原
型如下:

void iounmap (volatile void __iomem *addr)

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。

IO内存访问函数

这里说的 I/O 是输入/输出的意思,涉及到两个概念: I/O 端口和 I/O 内存。使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

读操作函数

u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)

readb、 readw 和 readl 这三个函数分别对应 8bit、 16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。

写操作函数

void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

writeb、 writew 和 writel 这三个函数分别对应 8bit、 16bit 和 32bit 写操作,参数 value 是要写入的数值, addr 是要写入的地址。

代码编写

新建文件夹,加入vscode工程,添加led.c ledApp.c文件。

led.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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_MAJOR   200       /* 主设备号 */
#define LED_NAME    "led"     /* 设备名字 */
#define LEDOFF      0   //关灯
#define LEDON       1   //开灯

/*  寄存器物理地址  */
#define CCM_CCGR1_BASE              (0x020C406C)
#define SW_MUX_GPIO1_IO04_BASE      (0x020E006C)
#define SW_PAD_GPIO1_IO04_BASE      (0x020E02F8)
#define GPIO1_DR_BASE               (0x0209C000)
#define GPIO1_GDIR_BASE             (0x0209C004)


/*   映射后的寄存器虚拟地址   */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO04;
static void __iomem *SW_PAD_GPIO1_IO04;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/*
@description    :   LED打开/关闭
@param - sta    :   LEDON(0)打开LED,LEDOFF(1)关闭LED
@return         :   无
*/
void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON)
    {
        val = readl(GPIO1_DR);
        val &= ~(1<<4);
        writel(val,GPIO1_DR);
    }
    else if(sta == LEDOFF)
    {
        /* code */
        val = readl(GPIO1_DR);
        val |= (1<<4);
        writel(val,GPIO1_DR);
    }
    
}


/*
@description      :   打开设备
@param - inode    :   传递给驱动的inode   
@param - filp     :   设备文件 
@return           :   0 成功; 其他 失败
*/
static int led_open(struct inode *inode, struct file *file)
{
    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 datebuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(datebuf, buf, cnt);
    if(retvalue < 0)
    {
        printk("kernel write failed!!\r\n");
        return -EFAULT;
    }

    ledstat = datebuf[0];

    if(ledstat  == LEDON)
    {
        led_switch(LEDON);
    }    
    else if (ledstat == LEDOFF)
    {
        led_switch(LEDOFF);
    }
    return 0;

}

/*
@description      :   关闭/释放设备  
@param - filp     :   要关闭的设备文件 
@return           :   0 成功; 其他 失败
*/
static int led_release(struct inode *inode, struct file *file)
{
    return 0;
}

/*   设备操作函数集合   */
static struct file_operations led_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 retvalue = 0;
    u32 val = 0;

    /*初始化LED*/
    /*1.寄存器地址映射*/
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
    SW_MUX_GPIO1_IO04 = ioremap(SW_MUX_GPIO1_IO04_BASE,4);
    SW_PAD_GPIO1_IO04 = ioremap(SW_PAD_GPIO1_IO04_BASE,4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);

    /*2.使能GPIO1始终*/
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);
    val |= (3 << 26);
    writel(val,IMX6U_CCM_CCGR1);

    /*3.设置GOIO1_IO04 的复用功能,将其复用为GPIO1_IO04,最后设置IO属性*/
    writel(5,SW_MUX_GPIO1_IO04);
    //设置IO属性
    writel(0x10B0,SW_PAD_GPIO1_IO04);
    
    //设置GPIO1_IO04为输出功能
    val = readl(GPIO1_GDIR);
    val &= ~(1<<4);
    val |= (1<<4);
    writel(val,GPIO1_GDIR);

    //默认关闭LED
    val = readl(GPIO1_DR);
    val |= (1<<4);
    writel(val,GPIO1_DR);

    //注册字符设备驱动
    retvalue = register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
    if(retvalue < 0)
    {
        printk("register chrdev failed!\r\n");

        return 0;
    }
    return 0;

}

static void __exit led_exit(void)
{
    //取消映射
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO04);
    iounmap(SW_PAD_GPIO1_IO04);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    //注销字符驱动
    unregister_chrdev(LED_MAJOR,LED_NAME);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

ledApp.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 0
#define LEDON  1

int main(int argc, char *argv[])
{
    int fd,retvalue;
    char *filename;
    unsigned char datebuf[1];

    if(argc != 3)
    {
        printf("error usage!\r\n");
        return -1;
    }

    filename = argv[1];

    //打开LED驱动
    fd = open(filename,O_RDWR);

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

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

    retvalue = write(fd,datebuf,sizeof(datebuf));

    if(retvalue < 0)
    {
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd);
    if(retvalue < 0)
    {
        printf("file %s close Failed!\r\n");
        return -1;
    }
    return 0;

    
}

Makefile

KERNELDIR := /home/book/arm/imx6ull/ebf_6ull_linux
CURRENT_PATH := $(shell pwd)
obj-m := led.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译运行测试

编译led.ko

输入指令:

sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

编译ledApp

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

运行测试

将生成的led.ko和ledApp文件拷贝到开发板目录 /lib/modules/4.15下。

  • 加载驱动模块
insmod led.ko
  • 创建/dev/led设备节点
mknod /dev/led c 200 0
  • 开灯
./ledApp /dev/led 1
  • 关灯
./ledApp /dev/led 0
  • 卸载驱动
rmmod led.ko
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值