linux下led驱动(非设备树)
目录
第一步地址映射
编写驱动前,简单了解一下MMU和虚拟地址空间
MMU全称Memory Management Unit内存管理单元,是一种硬件设备,用于管理内存访问权限和地址转换。
- 完成虚拟空间到物理空间的映射
- 内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性
针对于32位机来说

虚拟地址的范围是4G,而我们的开发板只有512MB的DDR内存,所以只能映射512MB的物理内存,经过MMU转换后,可以映射4G的虚拟地址空间。这个工作在linux内核启动时会做好工作,设置好后CPU访问的都是虚拟地址。
打个比方.MX6ULL 的 GPIO1_IO03 引脚的复用寄存器
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。如果没有开启 MMU 的话
直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启
了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必
须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内
存和虚拟内存之间的转换,需要用到两个函数:ioremap 和 iounmap。
ioremap
ioremap 函数用于将物理地址映射为虚拟地址,函数原型如下:
c
void *ioremap(unsigned long phys_addr, unsigned long size);
phys_addr 是要映射的物理地址,size 是要映射的内存大小。函数返回映射后的虚拟地址。
假如我们现在要获取该io口的虚拟地址
c
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;//定义一个指向io口的虚拟地址的指针
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);//将物理地址0X020E0068映射为虚拟地址
映射完后直接对SW_MUX_GPIO1_IO03 进行读写操作
iounmap
iounmap 函数用于取消之前通过 ioremap 映射的虚拟地址,函数原型如下:
c
void iounmap(void *addr);
addr 是要取消映射的虚拟地址。
假如我们现在要取消对0X020E0068的映射
c
iounmap(SW_MUX_GPIO1_IO03);
第二步内存访问函数
这里涉及两个概念:io端口和io内存
io端口:
当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。
io内存:
当外部寄存器或内存映射到内存空间时称为io内存。
在做完地址映射后,我们就可以直接对虚拟地址进行读写操作了,但是为了方便起见,linux提供了一些内存访问函数,比如readb,writeb,readl,writel等,这些函数可以直接对虚拟地址进行读写操作,而不需要我们自己去实现。
读操作函数
readb,readw,readl 分别用于读取 8 位、16 位和 32 位数据,函数原型如下:
c
u8 readb(void *addr);
u16 readw(void *addr);
u32 readl(void *addr);
addr 是要读取的虚拟地址。
写操作函数
writeb,writew,writel 分别用于写入 8 位、16 位和 32 位数据,函数原型如下:
c
void writeb(u8 value, void *addr);
void writew(u16 value, void *addr);
void writel(u32 value, void *addr);
value 是要写入的数据,addr 是要写入的虚拟地址。
一个简单的led驱动
需要查看一下芯片的驱动手册后进行编写
c
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 说明:
* CCM_CCGR1 bit[27:26] 控制 GPIO1 时钟门控;值 0b11 表示使能。
* SW_MUX_GPIO1_IO03 选择复用为 GPIO;写入 5 表示 ALT5(GPIO)。
* SW_PAD_GPIO1_IO03 配置上下拉、速度、驱动能力等 PAD 属性。
* GPIO1_GDIR 设置方向;GPIO1_DR 设置输出电平。
*/
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
//往后代码是在驱动的入口函数里编写
/* 初始化LED */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
/* bit[27:26] = 0b11 打开 GPIO1 时钟门控 */
val &= ~(3 << 26); /* 清除以前的设置 */
val |= (3 << 26); /* 设置为始终使能 */
writel(val, IMX6U_CCM_CCGR1);
/* 3、设置GPIO1_IO03的复用功能,将其复用为
* GPIO1_IO03,最后设置IO属性。
*/
writel(5, SW_MUX_GPIO1_IO03);
/* ALT5=GPIO 功能 */
/*寄存器SW_PAD_GPIO1_IO03设置IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 5、默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
下一节再讲解用设备树来编写led驱动