/*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);