cuter

【原创】Linux Platform设备及其驱动(1)

0
阅读(3822)

版权声明:

本文由电子技术应用博主“cuter”发布。欢迎转载,但不得擅自更改博文内容,也不得用于任何盈利目的。转载时不得删除作者简介和作者单位简介。如有盗用而不说明出处引起的版权纠纷,由盗用者自负。

博客官方地址:

http://blog.chinaaet.com/cuter521

http://bbs.ednchina.com/BLOG_cuter521_356737.HTM


题记:

虽有嘉肴,弗食,不知其旨也;虽有至道,弗学,不知其善也。是故学然后知不足,教然后知困。知不足,然后能自反也;知困,然后能自强也。故曰:教学想长也。《兑命》曰:“学学半。”其次之谓乎。


我们的文化真是博大精深,短短的几句话,精准地描述了教与学之间的关系。学学半——教人也是学习的一半。

读研的时候,带本科生的实验课,面对千奇百怪的提问,可以深刻的感受到自己的不足——不能够讲解地让别人理解,就说明自己的理解不够透彻,做不到庖丁解牛。

在写博文的时候,也有这种感觉,想要负责任地写出一篇文章,不仅仅要在技术上说服自己,还要思考用什么样的方式才能让读者更快更好地明白自己要说的东西,甚至要考虑篇幅多长才合适,文章难写啊……


闲话休提,书归正传。


1 Vivado中添加axi-gpio IP核,并挂载到axi-lite总线上

添加IP核后,可以利用指令自动完成axi-lite接口的连接。这里我把自动生成的时钟和复位信号发生模块删除了,保留着应该也没有影响。

在关闭Vivado之前,记得查看一下IP的物理地址,或者在SDK中查看。后面修改dts文件的时候会用到。

2 制作BOOT.bin文件

这里也不用多说,网上教程也很多。

3 编写驱动并编译生成*.ko文件

这里给出代码,编译方法参考上一篇博文

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>  /* Needed for copy_from_user */
#include <asm/io.h>  /* Needed for IO Read/Write Functions */
#include <linux/proc_fs.h>  /* Needed for Proc File System Functions */
#include <linux/seq_file.h> /* Needed for Sequence File Operations */ 

#include <linux/platform_device.h>  /* Needed for Platform Driver Functions */
 
/* Define Driver Name */
#define DRIVER_NAME "myled"
 
unsigned long *base_addr;  /* Vitual Base Address */
struct resource *res;  /* Device Resource Structure */
unsigned long remap_size;  /* Device Memory Size */
 
/* Write operation for /proc/myled
 * -----------------------------------
 * When user cat a string to /proc/myled file, the string will be stored in
 * const char __user *buf. This function will copy the string from user
 * space into kernel space, and change it to an unsigned long value.
 * It will then write the value to the register of myled controller,
 * and turn on the corresponding LEDs eventually.
 */
static ssize_t proc_myled_write(struct file *file, const char __user * buf,
size_t count, loff_t * ppos)
{
	char myled_phrase[16];
	u32 myled_value;

	if (count < 11)
	{
		if (copy_from_user(myled_phrase, buf, count))
			return -EFAULT;

		myled_phrase[count] = '\0';
	}

	myled_value = simple_strtoul(myled_phrase, NULL, 0);
	wmb();
	iowrite32(myled_value, base_addr);
	return count;
}
 
/* Callback function when opening file /proc/myled
 * ------------------------------------------------------
 * Read the register value of myled controller, print the value to
 * the sequence file struct seq_file *p. In file open operation for /proc/myled
 * this callback function will be called first to fill up the seq_file,
 * and seq_read function will print whatever in seq_file to the terminal.
 */
static int proc_myled_show(struct seq_file *p, void *v)
{
	u32 myled_value;
	myled_value = ioread32(base_addr);
	seq_printf(p, "0x%x", myled_value);
	return 0;
}
  

/* Open function for /proc/myled
 * ------------------------------------
 * When user want to read /proc/myled (i.e. cat /proc/myled), the open function 
 * will be called first. In the open function, a seq_file will be prepared and the 
 * status of myled will be filled into the seq_file by proc_myled_show function.
 */
static int proc_myled_open(struct inode *inode, struct file *file)
{
	unsigned int size = 16;
	char *buf;
	struct seq_file *m;
	int res;

	buf = (char *)kmalloc(size * sizeof(char), GFP_KERNEL);
	if (!buf)
		return -ENOMEM;
 
	res = single_open(file, proc_myled_show, NULL);
 
	if (!res)
	{
		m = file->private_data;
		m->buf = buf;
		m->size = size;
	}
	else
	{
		kfree(buf);
	}
 
	return res;
}
 
/* File Operations for /proc/myled */
static const struct file_operations proc_myled_operations = {
	.open = proc_myled_open,
	.read = seq_read,
	.write = proc_myled_write,
	.llseek = seq_lseek,
	.release = single_release
};
 
 /* Shutdown function for myled
  * -----------------------------------
  * Before myled shutdown, turn-off all the leds
  */
static void myled_shutdown(struct platform_device *pdev)
{
	iowrite32(0, base_addr);
}
  

 /* Remove function for myled
  * ----------------------------------  
  * When myled module is removed, turn off all the leds first,
  * release virtual address and the memory region requested.
  */
static int myled_remove(struct platform_device *pdev)
{
	myled_shutdown(pdev);
 
	/* Remove /proc/myled entry */
	remove_proc_entry(DRIVER_NAME, NULL);
 
	/* Release mapped virtual address */
	iounmap(base_addr);
 
	/* Release the region */
	release_mem_region(res->start, remap_size);
 
	return 0;
}
 
 /* Device Probe function for myled
  * ------------------------------------
  * Get the resource structure from the information in device tree.
  * request the memory regioon needed for the controller, and map it into
  *  kernel virtual memory space. Create an entry under /proc file system
  * and register file operations for that entry.
  */
static int __devinit myled_probe(struct platform_device *pdev)
{
	struct proc_dir_entry *myled_proc_entry;
	int ret = 0;
 
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) 
	{
		dev_err(&pdev->dev, "No memory resource\n");
		return -ENODEV;
	}
 
	remap_size = res->end - res->start + 1;
	
	if (!request_mem_region(res->start, remap_size, pdev->name))
	{
		dev_err(&pdev->dev, "Cannot request IO\n");
		return -ENXIO;
	}
 
	base_addr = ioremap(res->start, remap_size);
	if (base_addr == NULL)
	{
		dev_err(&pdev->dev, "Couldn't ioremap memory at 0x%08lx\n",
		(unsigned long)res->start);
		ret = -ENOMEM;
		goto err_release_region;
	}


	myled_proc_entry = proc_create(DRIVER_NAME, 0, NULL,&proc_myled_operations);
	if (myled_proc_entry == NULL) 
	{
		dev_err(&pdev->dev, "Couldn't create proc entry\n");
		ret = -ENOMEM;
		goto err_create_proc_entry;
	}
 
	printk(KERN_INFO DRIVER_NAME " probed at VA 0x%08lx\n",
	(unsigned long) base_addr);
 
	return 0;

	err_create_proc_entry:
	iounmap(base_addr);
	err_release_region:
	release_mem_region(res->start, remap_size);
	
	return ret;
} /* end of myled_probe*/
 
/* device match table to match with device node in device tree */
static const struct of_device_id myled_of_match[] __devinitconst =
{
	{.compatible = "dglnt,myled-1.00.a"},
	{},
};
 
 MODULE_DEVICE_TABLE(of, myled_of_match);
 
/*
platform 是一个虚拟的地址总线,相比 PCI、USB,它主要用于描述 SOC 上的片上资源。
比如 S3C2410 上集成的控制器( LCD、Watchdog、RTC等),
platform 所描述的资源有一个共同点:在 CPU 的总线上直接取址。
*/
 
/* platform driver structure for myled driver */
static struct platform_driver myled_driver =
{
	.driver =
	{
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = myled_of_match
	},
	.probe = myled_probe,
	.remove = __devexit_p(myled_remove),
	.shutdown = __devexit_p(myled_shutdown)
};

/* Register myled platform driver */
module_platform_driver(myled_driver);

/* Module Infomations */
MODULE_AUTHOR("Digilent, Inc.");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(DRIVER_NAME ": MYLED driver (Simple Version)");
MODULE_ALIAS(DRIVER_NAME);


4 修改dts文件,将设备信息添加至设备树

reg = <0x41200000 0x1000>;里面的两个参数分别对应axi_gpio的物理地址和寄存器空间大小

还有一个坑:

两个compatible属性的内容,是设备和驱动注册的关键,匹配时利用该字段进行对比。具体如何进行匹配,后续再深入剖析。

5 生成dtb文件

6 启动系统,加载驱动

以上两步,可以参考之前的博文,这里不再重复。

7 输入指令,操作led

实际上这是Digilent提供的教程,做完之后,是一头雾水,根本不明白到底怎么回事,稀里糊涂地就可以操作led了。

当时想的问题有:

i) 在学习最简单的Linux驱动时,有module_init()module_exit()函数,在加载和卸载驱动时会被调用,为什么这里没有?

ii) 这个好像和一般的设备驱动不太一样,什么是platform driver

iii) 搜索了一下platform driver,更加迷糊了,不过platform driver和普通的设备驱动是有区别的,既然开始了,就先研究platform driver吧。


~改天继续……