明确:1.LED属于字符设备驱动,2.关于字符设备的介绍在上篇文章中有介绍,3.关于GPIO的操作在之前文章中也有介绍。
字符设备驱动介绍:https://blog.csdn.net/qq_35947329/article/details/102709469
I.MX6UL IO分析: https://blog.csdn.net/qq_35947329/article/details/102594242
问题一:点亮一个LED灯需要配置寄存器,在linux下也是直接操作地址吗?
在编写驱动之前,我们需要先简单了解一下 MMU(内存管理单元)
1.MMU主要功能有:完成虚拟空间到物理空间的映射。
2.内存保护,设置存储器访问权限。
linux中主要通过虚拟地址来访问物理地址。
问题二:如何通过虚拟地址获取物理地址?
ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间函数原型如下:
#define ioremap(cookie,size) __arm_ioremap((cookie), (size),MT_DEVICE)
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype,__builtin_return_address(0));
}
ioremap 是个宏,有两个参数:cookie 和 size,真正起作用的是函数__arm_ioremap,此函数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr:要映射给的物理起始地址。
size:要映射的内存空间大小。
mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、MT_DEVICE_CACHED MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。
例子:
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(GPIO1_GDIR_BASE, 4);
即可获得I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应的虚拟地址
看起来获取虚拟地址就像申请了一个内存,有申请就应该有释放,当卸载驱动的时候需要把虚拟地址释放掉,释放虚拟地址的函数是iounmap,函数原型如下:
void iounmap (volatile void __iomem *addr)
该函数只有一个参数,addr,此参数即为要释放的虚拟地址。
假如我们现在要取消掉 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,使用如下代码即可:
iounmap(SW_MUX_GPIO1_IO03);
问题三:是不是直接对虚拟地址赋值就能操作相应寄存器?
在linux下不建议直接对地址进行操作。而是通过读写函数。
1 、读操作函数
读操作函数有如下几个:
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。
2 、写操作函数
写操作函数有如下几个:
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作,参数 value 是要写入的数值,addr 是要写入的地址。
例如:
#define GPIO1_DR_BASE (0X0209C000)
static void __iomem * GPIO1_DR ;
32 val = 0;
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
这段话的含义是将GPIO1_DR_BASE 寄存器的物理地址映射到GPIO1_DR虚拟地址。
通过虚拟地址读取GPIO1_DR_BASE寄存器的值(readl)。
通过虚拟地址向GPIO1_DR_BASE寄存器赋值(writel)
问题四:如何编写完整的LED驱动?
LED需要准备工作:
1.LED初始化(打开时钟,设置IO复用,设置IO为输出模式,设置上下拉和速度)
2.LED操作,使其输出高电平或低电平。
结合字符设备驱动模型,我们应该能够想到将LED初始化放在字符设备加载函数中
将LED操作放在设备操作集中(write响应函数)
问题五:剩余工作?
重要事情说三遍!!!
既然我们使用了虚拟内存就应该再不用的时候释放掉。因此在驱动卸载函数中要做寻你内存的释放!!!
既然我们使用了虚拟内存就应该再不用的时候释放掉。因此在驱动卸载函数中要做寻你内存的释放!!!
既然我们使用了虚拟内存就应该再不用的时候释放掉。因此在驱动卸载函数中要做寻你内存的释放!!!
总结:
1.linux下一般使用虚拟地址来访问物理地址。
2.linux一般不直接对地址操作,而是使用读写函数。
3.驱动卸载的时候记得把申请的虚拟内存释放掉。
4.结合字符设备驱动模型吧LED初始化和LED操作函数插入到相应位置