Liunx驱动开发学习 第二节 寄存器地址配置与自动挂载

/*newchrled.c*/
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/cdev.h>
#include<linux/device.h>

/*
    Linux下操作的都是虚拟地址,需要找到物理地址对应的虚拟地址
    获得物理地址对应的虚拟地址使用ioremap函数
    第一个参数是物理地址实际大小,第二个参数就是要转化的字节数量,
    这里IMX6U的寄存器是4个字节32位的,那么第二个参数size为4
    iounmap,虚拟地址卸载
*/
#define LEDON   0
#define LEDOFF  1


#define NEWCHRLED_CNT       1
#define NEWCHRLED_NAME      "NEWCHRLED"

/*寄存器物理地址*/
#define CCM_CCGR1_BASE              (0X020C406C)
#define SW_MUX_CPIO1_IO03_BASE      (0X020E0068)
#define SW_PAW_GPIO1_IO03_BASE      (0X020E02F4)
#define GPIO1_DR_BASE               (0X0209C000)
#define GPIO1_GDIR_BASE             (0X0209C004)

/*地址映射后的虚拟地址指针*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAW_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

struct newchrled_dev
{
    struct cdev     cdev;       //字符设备
    struct class    *class;     //类
    struct device   *device;    //设备     
    dev_t           devid;      //设备号
    int             major;      //主设备号
    int             minor;      //次设备号
};

struct newchrled_dev newchrled ={
    .minor = 0,
};



static int newchrled_open(struct inode *inode, struct file *file)
{

    return 0;
}

static int newchrled_release(struct inode *inode, struct file *file)
{
    printk("release device\r\n");

    return 0;
}

static ssize_t newchrled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int retvalue;
    unsigned char databuf[1];

    retvalue = copy_from_user(databuf, buf, count);
    return 0;
}

static struct file_operations newchrled_fops={
    .owner = THIS_MODULE,
    .open = newchrled_open,
    .release = newchrled_release,
    .write = newchrled_write,
};

static int __init newchrled_init(void)
{
    int ret = 0;
    unsigned int val;
    /*1.初始化LED灯,地址映射*/
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_CPIO1_IO03_BASE,4);
    SW_PAW_GPIO1_IO03 = ioremap(SW_PAW_GPIO1_IO03_BASE,4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);

    /*2.初始化*/
    val = readl(IMX6U_CCM_CCGR1);//读取IMX6U_CCM_CCGR1寄存器的数据
    val |= (3<<26);             //第26,27位 置1
    writel(val, IMX6U_CCM_CCGR1),//写入数据到IMX6U_CCM_CCGR1寄存器

    writel(0x5, SW_MUX_GPIO1_IO03);
    writel(0x10b0, SW_PAW_GPIO1_IO03);

    val = readl(GPIO1_GDIR);
    val |= 1<<3;
    writel(val, GPIO1_GDIR);//设置GPIO1_IO03为输出

    val = readl(GPIO1_DR);
    val |= 1<<3;
    writel(val, GPIO1_DR);//选定设置为高电平

    /* 3.注册/申请设备号*/
    if(newchrled.major) //给定了主设备号
    {
        //生成设备号,一般次设备号为0
        newchrled.devid = MKDEV(newchrled.major, newchrled.minor); 
        //注册设备,设备号newchrled.devid,申请数量 NEWCHRLED_CNT,一般都是申请一个, 设备名称 NEWCHRLED_NAME
        ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
    }
    else //没有给定,需要自己申请
    {   //申请设备号,次设备号从0开始,申请NEWCHRLED_CNT个,名称NEWCHRLED_NAME
        ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.minor);
    }

    if(ret < 0) goto fail_cdev;//注册失败

    /*4.注册字符设备*/
    newchrled.cdev.owner = THIS_MODULE;
    //初始化字符设备newchrled.cdev,newchrled_fops是字符设备文件操作函数集合
    cdev_init(&newchrled.cdev,&newchrled_fops);
    //添加字符设备newchrled.cdev, 设备使用的设备号newchrled.devid, 添加数量NEWCHRLED_CNT
    ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
    if(ret < 0) goto fail_cdev;

    /*5.自动创建设备节点,无需每次手动加载节点*/
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
    if(IS_ERR(newchrled.class)) 
    {
        ret = PTR_ERR(newchrled.class);
        goto fail_class;
    } 

    newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
    if(IS_ERR(newchrled.device)) 
    {
        ret = PTR_ERR(newchrled.device);
        goto fail_device;
    }

    return 0;

    fail_device:
        class_destroy(newchrled.class);
    fail_class:
        cdev_del(&newchrled.cdev);
    fail_cdev:
        unregister_chrdev_region(newchrled.devid,NEWCHRLED_CNT);
        printk("ERROR chrdev_region");
        return ret;
}

static void __exit newchrled_exit(void)
{
    /*1.取消地址映射*/
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAW_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
    /*2.摧毁设备*/
    device_destroy(newchrled.class, newchrled.devid);
    /*3.摧毁类*/
    class_destroy(newchrled.class);
    /*4.删除字符设备*/
    cdev_del(&newchrled.cdev);
    /*5.注销设备号*/
    unregister_chrdev_region(newchrled.devid,NEWCHRLED_CNT);
}

module_init(newchrled_init);
module_exit(newchrled_exit);

MODULE_LICENSE("GPL");

一、寄存器地址配置

1.地址映射

Linux下操作的都是虚拟地址,需要找到物理地址对应的虚拟地址,获得物理地址对应的虚拟地址使用ioremap函数。以后不管读写都是使用虚拟地址。第一个参数是物理地址,第二个参数就是要转化的字节数量,这里IMX6U的寄存器地址是4个字节32位的,那么第二个参数size为4。返回虚拟地址

IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);

2.读写寄存器的数据

用readl读取对应寄存器的数据,输入寄存器地址,返回寄存器数据。注意val的位数,需要根据寄存器数据的实际位数来决定,这里寄存器数据大小是32位,因此采用无符号整型

unsigned int val;
val = readl(IMX6U_CCM_CCGR1);//读取IMX6U_CCM_CCGR1寄存器的数据

用writel写入数据到对应的地址,第一个参数为需要写入的数据,第二个参数是对应的寄存器数据。

unsigned int val;
val |= (3<<26);		//第26,27位 置1
writel(val, IMX6U_CCM_CCGR1)//写入数据到IMX6U_CCM_CCGR1寄存器

在卸载设备的时候要把对应的地址映射也一并卸载。

iounmap(IMX6U_CCM_CCGR1);

二、注册/申请设备号

原本的register_chrdev注册函数,每次注册都需要提前知道空闲的主设备号,并且会把主设备号下所有的次设备号都占用。新字符设备注册函数register_chrdev_region只占用对应主设备号下对应的一个次设备号。同时也可以自动申请设备号alloc_chrdev_region。这两个函数不可同时使用。

    if(newchrled.major) //给定了主设备号
    {
        //生成设备号,一般次设备号为0
        newchrled.devid = MKDEV(newchrled.major, newchrled.minor); 
        //注册设备,设备号newchrled.devid,申请数量 NEWCHRLED_CNT,一般都是申请一个, 设备名称 NEWCHRLED_NAME
        ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
    }
    else //没有给定,需要自己申请
    {   //申请设备号,次设备号从0开始,申请NEWCHRLED_CNT个,名称NEWCHRLED_NAME
        ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
        newchrled.major = MAJOR(newchrled.devid);//提取主设备号
        newchrled.minor = MINOR(newchrled.minor);//提取次设备号
    }

注销设备号函数unregister_chrdev_region,第一个参数为设备号,第二个参数是需要注销的个数

unregister_chrdev_region(newchrled.devid,NEWCHRLED_CNT);

三、注册字符设备

需要创建对应的字符设备结构体变量,我这个结构体变量是定义在一个自创建的结构体内。先cdev_init初始化字符设备,在cdev_add添加字符设备。

struct cdev     cdev;       //字符设备
    newchrled.cdev.owner = THIS_MODULE;
    //初始化字符设备newchrled.cdev,newchrled_fops是字符设备文件操作函数集合
    cdev_init(&newchrled.cdev,&newchrled_fops);
    //添加字符设备newchrled.cdev, 设备使用的设备号newchrled.devid, 添加数量NEWCHRLED_CNT
    ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

对应的在卸载的时候也需要cdev_del销毁字符设备

cdev_del(&newchrled.cdev);

四、自动创建设备节点,无需手动加载

加载驱动程序以后还需要使用命令“mknod”手动创建设备节点。本节就来讲解一下如何实现自动创建设备节点,在驱动中实现自动创建设备节点的功能后, 加载驱动模块成功的话就会自动在/dev 目录下创建对应的设备文件。
主要分两步,创建类和创建设备

1.class_create创建类

使用class_create来创建类,第一个输入参数为宏THIS_MODULE,第二个参数为设备名称,返回创建类的指针。

struct class    *class;     //类
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);

2.device_create创建设备

使用device_create创建设备,这里重要的参数为:被依赖的类newchrled.class,设备号newchrled.devid,设备名称NEWCHRLED_NAME。返回创建设备的指针。

struct device   *device;    //设备     
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);

3.销毁

注意按顺序,因为设备时依赖类的,所以先销毁设备再销毁类。
device_destroy第一个参数是设备依赖的类,第二个参数是设备号

    device_destroy(newchrled.class, newchrled.devid);
    class_destroy(newchrled.class);
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值