内存: 1GB 存储: 4GB
详细参数 https://m.tb.cn/h.3wMaSKm
开发板交流群 641395230原理图:
参考资料:
寄存器位置



一些概念:
字符设备驱动框架
必须有一个设备号,用在众多到设备驱动中进行区分,
用户必须知道设备驱动对应到设备节点(设备文件),linux把所有到设备都看成文件
对设备操作其实就是对文件操作,应用空间操作open,read,write的时候实际在驱动代码有对应到open, read,write
字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
Linux 2.6 标准字符设备核心结构
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; /* 设备文件操作方法 */
struct list_head list;
dev_t dev; /* 32 位设备号:包含主和次 */
unsigned int count; /*占用多少个连续的次设备号 */
};
Linux2.6版本以上内核,使用dev_t来表示一个设备号,dev_t实际上是一个u32类型。其中高12位是主设备号,低20位是次设备号。
主设备号:dev_t 高 12 位, 2^12=4K (10 是给杂项设备使用)范围 0 ~ 4095
次设备号: dev_t 低 20 位, 2^20=1M 范围 0 ~ 1M-1
合成设备号:
MKDEV(A,B):A: 主设备号; B: 次设备号
分解设备号:
MAJOR(dev) : 从设备号 dev 中分解出主设备号
MINOR(dev) : 从设备号 dev 中分解出次设备号
alloc_chrdev_region函数,来让内核自动给我们分配设备号
(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。 mknod /dev/xxx c 主设备号 次设备号
第二个参数:次设备号的基准,从第几个次设备号开始分配。
第三个参数:次设备号的个数。
第四个参数:驱动的名字。
返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。
int copy_from_user(void * to, const void __user * from, unsigned long n)
将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用 参数1:内核驱动中的一个buffer
参数2:应用空间到一个buffer
参数3:个数
自动创建设备结点
struct class *class_create(owner, name)//创建一个类
参数1: THIS_MODULE
参数2: 字符串名字,自定义
返回一个class指针
//创建一个设备文件
struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt,...)
参数1: class结构体,class_create调用之后到返回值
参数2:表示父亲,一般直接填NULL
参数3: 设备号类型 dev_t
dev_t devt
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
参数4:私有数据,一般直接填NULL
参数5和6:表示可变参数,字符串,表示设备节点名字
驱动代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#define MY_DEVICE_NAME "my_led_device"
//物理地址
#define GPL_BASE 0x01F02C00
volatile unsigned long *gpl_base = NULL;
volatile unsigned long *gpl_r0 = NULL;
volatile unsigned long *gpl_r1 = NULL;
volatile unsigned long *gpl_dat = NULL;
volatile unsigned long *gpl_pul0 = NULL;
//setp 3 :实现函数
static int my_open (struct inode *node, struct file *filp)
{
if (GPL_BASE)
{
printk("ioremap 0x%x\n", GPL_BASE);
}
else
{
return -EINVAL;
}
return 0;
}
static ssize_t my_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
unsigned char val;
copy_from_user(&val, buf, 1);
if (val)
{
*gpl_dat |= ( (1<<7) | (1<<10) | (1<<12) );
}
else
{
*gpl_dat &= ( ~(1<<7) | ~(1<<10) | ~(1<<12) );
}
return 1;
}
static const struct file_operations my_led_fops = {
//step 1 :定义file_operations结构体
.open = my_open,
.write = my_write,
};
//step 1 :
static struct class *led_class;
static struct cdev *pcdev; //定义一个cdev指针
static dev_t n_dev; //第一个设备号(包含了主和次)
static int __init led_device_init(void)
{//step 2 :注册
int ret = -1;
pcdev = cdev_alloc();//分配cdev结构空间
if(pcdev == NULL) {
printk(KERN_EMERG" cdev_alloc error\n");
ret = -ENOMEM; /* 分配失败 */
return ret;
}
//2. 动态申请设备号
ret = alloc_chrdev_region(&n_dev, 0 , 2, MY_DEVICE_NAME);
if(ret < 0 ) {
//释放前面成功的资源
kfree(pcdev); /*释放cdev结构空间 */
printk(KERN_EMERG"alloc_chrdev_region error\n");
return ret;
}
cdev_init(pcdev, &my_led_fops); //初始化cdev结构 /* 建立cdev和file_operations之间的连接 */
/*
或这样初始化cdev结构
pcdev->owner = THIS_MODULE;
pcdev->ops = &my_led_fops;
*/
ret = cdev_add(pcdev, n_dev, 2) ;//4. 向内核里面添加一个驱动,注册驱动
if(ret < 0 ) {
//释放前面成功的资源
unregister_chrdev_region(n_dev, 2); /* 释放前面申请的调和号*/
kfree(pcdev); /* 释放cdev结构空间 */
printk(KERN_EMERG"alloc_chrdev_region error\n");
return ret;
}
/*自动创建设备节点/dev/SinlinxA64_LED*/
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, n_dev, NULL, "SinlinxA64_LED");
/*虚拟地址映射*/
gpl_base = (volatile unsigned long *)ioremap(GPL_BASE, 1);
gpl_r0 = gpl_base;
gpl_r1 = gpl_base + 0x04;
gpl_dat = gpl_base + 0x10;
gpl_pul0 = gpl_base + 0x1c;
/* 初始化寄存器 */
*gpl_r0 &= ~(7<<28);
*gpl_r1 &= ( ~(7<<16) | ~(7<< 8) );
*gpl_dat &= ( ~(1<<7) | ~(1<<10) | ~(1<<12) );
*gpl_pul0 &= ( ~(3<<14) | ~(3<<20) | ~(3<<24) );
/*上拉输出*/
*gpl_r0 |= (1<<28);
*gpl_r1 |= ( (1<<16) | (1<<8) );
*gpl_pul0 |= ( (1<<14) | (1<<20) | (1<<24) );
printk(KERN_EMERG"cdev ok\n");
return 0;
}
static void __exit led_device_exit(void)
{ //step 2 :注销
//注销cdev结构
cdev_del(pcdev);
//释放设备号
unregister_chrdev_region(n_dev, 2); /*起始设备号(主、次) 连续的次设备号数量*/
//释放cdev结构空间
kfree(pcdev);
device_destroy(led_class, n_dev);
class_destroy(led_class);
iounmap(GPL_BASE);
printk(KERN_EMERG"cdev_del ok\n");
}
module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
我要赚赏金
