【linux-IMX6ULL-LED字符驱动框架完善】

1.简介

  在上节,我对linux-IMX6ULL-字符设备驱动简单框架实验进行了说明和构建,但是也存在几个问题;

  • 需要手动指定设备号,不能自动申请;
  • 需要在linux端手动创造设备节点,也就是要用maknod命令;
  • 没有引入实际设备;
      因此这节内容就根据上节的驱动框架,然后结合LED,实现设备号的自动分配和设备节点的自动创建;

2.前置知识

  由于本篇博客不属于教程类博客,只是作为学习总结和复盘,因此先把相关的重点知识给提前说明,也能起到一个便于快速回顾的目的;

2.1 重要函数及结构体

 下面的函数均进行了实参带入,具体原定义可以参考源码;

  • static void __iomem *IMX6U_CCM_CCGR1;
  • IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
  • register_chrdev_region(newchrled.devid,NEWCHRLED_COUNT,NEWCHRLED_NAME);
  • alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_COUNT,NEWCHRLED_NAME);
  • struct cdev cdev;
  • struct class *class;
  • struct device *device;
  • cdev_init(&newchrled.cdev, &newchrled_fops);
  • cdev_add(&newchrled.cdev, newchrled.devid,1);
  • class_create(THIS_MODULE, NEWCHRLED_NAME);
  • device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);

2.2 程序框架流程

3. 代码详解:

  注意几个点:

  1. 在写驱动程序时不能直接操控物理寄存器,我们只能操控虚拟化的地址,然后虚拟化的地址通过映射间接操控真实的寄存器;
  2. 操控虚拟化的寄存器地址时是通过read(),write()函数来完成的,不能直接赋值;
  3. 我们接收用户端的写的数据时要通过copy_from_user(databuf,buffer,count)函数来实现,不能直接获取;
  4. 注意出口函数里面的注销和删除顺序是有要求的,我们最开始是先注册的设备号,然后注册操作结构体,但是我们在出口函数里面是先删除操作结构体,然后再删除设备号,注意顺序是有要求的,其它也是一样的;

#define LED_MAJOR 200  
#define NEWCHRLED_NAME "newchrled1"
#define NEWCHRLED_COUNT 1

/*物理地址*/
#define CCM_CCGR1_BASE				(0x020C406C)
#define SW_MUX_GPIO1_IO03_BASE		(0x020E0068)
#define SW_PAD_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_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/*宏定义,开关*/
#define	LEDOFF		0
#define LEDON		1

/**LED 设备结构体**/
struct newchrled_dev{

	struct cdev cdev;     /*创建设备结构体*/
	struct class *class;  /*返回值都是指针类型*/
	struct device *device; /*创建设备的返回值,是个结构体指针*/
	dev_t devid;			/*设备号*/
	int major;				/*主设备号*/
	int minor;			/*次设备号*/
};
/*创建LED设备的结构体,这里没有初始化*/
struct newchrled_dev newchrled;
/*led开关函数封装*/
void led_switch(u8 sta)
{
	u32 val=0;
	if(sta==LEDON){
		val = readl(GPIO1_DR);
		val &= ~(1<<3);
		writel(val,GPIO1_DR);
	}
	if(sta==LEDOFF){
		val = readl(GPIO1_DR);
		val |= (1<<3);
		writel(val,GPIO1_DR);
	}
}
/*led初始化封装*/
void led_inti(void)
{
	unsigned int val = 0;
	/*把物理地址进行虚拟化映射,映射完后把虚拟地址赋值给前面定义的虚拟地址*/
	IMX6U_CCM_CCGR1 	= ioremap(CCM_CCGR1_BASE,4);
	SW_MUX_GPIO1_IO03 	= ioremap(SW_MUX_GPIO1_IO03_BASE,4);
	SW_PAD_GPIO1_IO03 	= ioremap(SW_PAD_GPIO1_IO03_BASE,4);
	GPIO1_DR 			= ioremap(GPIO1_DR_BASE,4);
	GPIO1_GDIR 			= ioremap(GPIO1_GDIR_BASE,4);
	/*开时钟*/
	val=readl(IMX6U_CCM_CCGR1);
	val &= ~(3<<26);/*clear*/
	val |= (3<<26);/*set bit 27 26 into 1*/
	writel(val,IMX6U_CCM_CCGR1);
	/*配置寄存器*/
	writel(0x5,SW_MUX_GPIO1_IO03);
	writel(0x10B0,SW_PAD_GPIO1_IO03);
	val = readl(GPIO1_GDIR);
	val |= (1<<3);
	writel(val,GPIO1_GDIR);
}


static int newchrled_release(struct inode *inode, struct file *file)
{
	printk("Close ok\r\n");
	//struct newchrled_dev *dev=(struct newchrled_dev*)file->private_data;
	return 0;
}

static int newchrled_open(struct inode *inode, struct file *file)
{
	printk("Open ok\r\n");
	//file->private_data = &newchrled;
	return 0;
}


static ssize_t newchrled_write(struct file *file, const char __user *buffer,size_t count, loff_t *pos)
{
	unsigned int retvalue;
	unsigned char databuf[1];
	/*从用户哪里获取写入的数据,这里不能直接获得,要通过下面的函数进行获得*/
	retvalue=copy_from_user(databuf,buffer,count);
	if(retvalue<0)
	{
		printk("Kernel write failed!\r\n");
		return -EFAULT;
	}
	/*判断是开灯还是关灯*/
	led_switch(databuf[0]);
	return 0;
}

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


/**into**/
static int __init newchrled_init(void)
{
	int ret = 0;
	printk("newchrled init!\r\n");
	/*1.初始化LED灯,地址映射*/	
	led_inti();

	/*2.注册设备号*/
	newchrled.major = 0;
	if(newchrled.major){
		newchrled.devid = MKDEV(newchrled.major,0);
		ret = register_chrdev_region(newchrled.devid,NEWCHRLED_COUNT,NEWCHRLED_NAME);
	}else{
		ret = alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_COUNT,NEWCHRLED_NAME);
		newchrled.major = MAJOR(newchrled.devid);
		newchrled.minor = MINOR(newchrled.devid);
	}
	if(ret<0){
		printk("newchrled chrdev err!\r\n");
		return -1;
	}
	printk("major=%d,minor=%d\r\n",newchrled.major,newchrled.minor);

	/*3 注册操作函数*/
	newchrled.cdev.owner=THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);
	cdev_add(&newchrled.cdev, newchrled.devid,1);/*添加到linux内核中*/
	//  第二步和第三歩本来在前两节是通过下面的函数实现的:
	//  register_chrdev(LED_MAJOR, LED_NAME,&led_fops);
	//  这里改写成了改写成了两步,第一步是申请设备号,第二步是注册设备操作函数

	/*4.自动创建设备节点*/
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)){
		return PTR_ERR(newchrled.class);
	}

	/*5.创建一个设备*/
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid,NULL,NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device))
	{
		return PTR_ERR(newchrled.device);
	}
	return 0;
}


/**exit**/
static void __exit newchrled_exit(void)
{
	printk("newchrled exit!\r\n");
	/*1.注销字符操作函数*/
	cdev_del(&newchrled.cdev);
	/*2.注销设备号*/
	unregister_chrdev_region(newchrled.devid,NEWCHRLED_COUNT);
	/*3.先摧毁设备*/
	device_destroy(newchrled.class, newchrled.devid);
	/*4.后摧毁类*/
	class_destroy(newchrled.class);
}

module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值