裸机控制硬件的流程:
分析原理图–>找到控制硬件的GPIO–>找GPIO的寄存器—>分析寄存器—>理解寄存器的控制顺序—>通过寄存器的地址来访问该寄存器
注意:
裸机使用的是物理地址,所以直接使用CPU手册查到的地址可以编程。
linux驱动使用的虚拟地址,不能直接使用物理地址。想办法,如果通过CPU手册查到的物理地址找到其对应虚拟地址?
内核一般分成两个过程:
申请物理地址区作为一个资源----->将物理内存区做内存的动态映射,得到虚拟地址。
注意:
资源—有限的,一旦一个物理内存区已经申请了,后面就不能再次申请。
1.申请物理内存区作为资源
struct resource * request_mem_region(resource_size_t start, resource_size_t n, const char *name)
参数说明:
resource_size_t start --->物理内存区的开始地址
resource_size_t n --->物理内存区的大小
const char *name --->自定义的物理内存区的名字
返回值:
struct resource * --->物理内存区作为了资源
思考:
LED驱动,申请哪个物理内存区?
D8–>GPIOC17,D9–>GPIOC8,D10–>GPIOC7,D11–>GPIOC12
start address —>0xC001C000
address size —> 结束地址:0xC001CFFF,大小:0x1000
name —>“GPIOC_MEM”
2.释放申请的物理内存区
void release_mem_region(resource_size_t start, resource_size_t n)
3.IO内存动态映射
#include <linux/io.h>
将一段物理地址内存区映射成一段虚拟地址内存区
void __iomem *ioremap(phys_addr_t offset, unsigned long size)
参数说明:
phys_addr_t offset --->要映射的物理内存区开始地址
unsigned long size --->物理内存区的大小
返回值:
void __iomem * --->映射后,虚拟地址内存区的首地址
3.解除IO内存动态映射
void iounmap(void __iomem *addr)
4.使用虚拟地址
①得到虚拟地址
gpioc_base_va = ioremap(phys_addr_t offset, unsigned long size)
if(gpioc_base_va == NULL){
printk("ioremap error\n");
release_mem_region(0xC001C000, 0x1000);
device_destroy(leds_class, led_num);
class_destroy(leds_class);
cdev_del(&gec6818_led_cdev);
unregister_chrdev_region(led_num, 1);
return -EBUSY;
}
//得到每个寄存器的虚拟地址
gpiocout_va = gpioc_base_va + 0x00;
gpiocoutenb_va = gpioc_base_va + 0x04;
gpiocaltfn0_va = gpioc_base_va + 0x20;
gpiocaltfn1_va = gpioc_base_va + 0x24;
gpiocpad_va = gpioc_base_va + 0x18;
②虚拟地址的类型:void __iomem *
③访问虚拟地址的方法:与访问物理地址的方法一样
//1.访问虚拟地址
//1.1 GPIOC7,8.12,17 --->function1,作为普通的GPIO
*(unsigned int *)gpiocaltfn0_va &=~((3<<14)|(3<<16)|(3<<24));
*(unsigned int *)gpiocaltfn1_va &=~(3<<2);
*(unsigned int *)gpiocaltfn0_va |= ((1<<14)|(1<<16)|(1<<24));
*(unsigned int *)gpiocaltfn1_va |= (1<<2);
//1.2 GPIOC7,8.12,17 --->设置为输出
*(unsigned int *)gpiocoutenb_va |= ((1<<7)|(1<<8)|(1<<12)|(1<<17));
//1.3 GPIOC7,8.12,17 --->设置为输出高电平,D8~D11 off
*(unsigned int *)gpiocout_va |= ((1<<7)|(1<<8)|(1<<12)|(1<<17));
④虚拟地址的访问方法:使用内核提供的函数
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
void writel(u32 b, volatile void __iomem *addr)
或者:
void __raw_writel(u32 b, volatile void __iomem *addr)
u32 __raw_readl(const volatile void __iomem *addr)