字符设备驱动实现二之微机总线物理地址寄存器配置
一、微机总线物理地址
1、总线地址
百度百科介绍:
地址总线 (Address Bus;又称:位址总线) 属于一种电脑总线 (一部份),是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。
总线地址:
一句话概括:CPU能够访问内存的范围。
一个现象可知:一台电脑装了32位的系统,内存条明明是8G,可是系统只能识别3.8G,而装了64位系统的电脑才能识别8G。
由下面数据分析:
32位能表示/访问 4,294,967,296 bit
4,294,967,296 bit
4,194,304 kbit
4,096mbit
4gbit
pi@raspberrypi:~ $ cat /proc/meminfo
MemTotal: 949448 kB
MemFree: 632848 kB
树莓派是32位系统 ,MemTotal大概:1G
2、物理地址
硬件的实际地址或绝对地址。
3、虚拟地址
逻辑(基于算法的地址(软件层面的地址,假))地址称为虚拟地址。
4、树莓派3b,CPU芯片的型号BCM2835;ARM-cotexA53架构
总线地址 | 物理地址 | 虚拟地址 |
---|---|---|
起始值 0x00000000 | 起始值 0x3f200000 | 起始值 0x00000000 |
树莓派3b,CPU芯片的型号BCM2835;它是ARM-cotexA53架构
2440 2410 CUP型号 ; ARM9架构
5、驱动两大利器:电路图和芯片手册
5.1、通过芯片手册第六章导读:
作用:获得相关寄存器功能的配置
寄存器 | 作用 | 功能 | 范围 |
---|---|---|---|
GPFSEL0 | GPIO Function Select 0 (设置输入输出) | 输出/输入 | pin0~pin9 |
GPSET0 | GPIO Pin Output Set 0 置位寄存器 | 置1输出0寄存器 (置0无效0 | SETn (n=0…31) |
GPSET1 | GPIO Pin Output Set 1 置位寄存器 | 置1输出1寄存器 (置0无效 | SETn (n=32…53) |
GPCLR0 | GPIO Pin Output Clear Registers 清零寄存器 | 清除 | CLRn (n=0…31 |
5.2、电路图
作用:通过电路图找到寄存器
5.3、配置寄存器
GPIO有41个寄存器。所有访问都设定为32位。
先要获得以下寄存器的物理地址:
有图可得偏移值
寄存器 | 偏移值 | 物理地址 |
---|---|---|
GPFSEL0 | 00 | 0x3f200000 |
GPSET0 | 1c | 0x3f20001c |
GPSET1 | 20 | 0x3f200020 |
GPCLR0 | 28 | 0x3f2000028 |
int __init pin4_drv_init(void) //真实的驱动入口
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
GPFSEL0 = (volatile unsigned int*)ioremap(0x3f200000);//物理地址转发成虚拟地址,io寄存器进行映射成普通内存单元进行访问
GPSET0 = (volatile unsigned int*)ioremap(0x3f20001c);
GPCLR0 = (volatile unsigned int*)ioremap(0x3f200028);
return 0;
}
以树莓派引脚4为例
实现简单字符驱动代码:
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
static int pin4_read(struct file *file,char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_read\n"); //内核的打印函数和printf类似
return 0;
}
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
*GPFSEL0 &= ~(0X6 << 12); //设置pin4为输出功能
*GPFSEL0 |= (0X1 << 12);
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int usercmd;
printk("pin4_write\n"); //内核的打印函数和printf类似
copy_from_user(&usercmd,buf,count);
if(usercmd == 1){
printk("set 1\n");
*GPSET0 |= 0X1 << 4;
}else if(usercmd == 0){
printk("set 0\n");
*GPCLR0 |= 0X1 <<4;
}else{
printk("usrcmd failed\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
int __init pin4_drv_init(void) //真实的驱动入口
{
int ret;
devno = MKDEV(major,minor); //创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件
GPFSEL0 = (volatile unsigned int*)ioremap(0x3f200000,4);//物理地址转发成虚拟地址,io寄存器进行映射成普通内存单元进行访问
GPSET0 = (volatile unsigned int*)ioremap(0x3f20001c,4);
GPCLR0 = (volatile unsigned int*)ioremap(0x3f200028,4);
return 0;
}
void __exit pin4_drv_exit(void)
{
iounmap(GPFSEL0); //释放映射内存
iounmap(GPSET0);
iounmap(GPCLR0);
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //入口,内核加载驱动程序时候,这个宏被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");