一、前言
MIO也属于是字符设备,将MIO复用为GPIO可以实现对LED等外设的控制,其本质是从寄存器层面对硬件的控制。这次主要记录一下GPIO控制LED的驱动。
官网ZYNQ寄存器手册https://docs.amd.com/r/en-US/ug1087-zynq-ultrascale-registers/Overview
二、物理地址与虚拟地址
1、物理地址
物理地址是内存(包括寄存器等存储器件)在硬件层面的真实地址,由硬件决定的,是固定不变的。对于存储器件来说,物理地址唯一地标识了一个存储单元,CPU 可以通过物理地址直接访问内存中的数据。不过,在一些复杂的系统架构中,如具有内存管理单元(MMU)的系统,物理地址的访问可能会受到限制或者重新映射。
2、虚拟地址
虚拟地址是操作系统(如 Linux)为应用程序提供的一种抽象的地址空间。它是应用程序所看到的内存地址,和物理地址没有直接的对应关系。主要优点是提供了内存保护和多任务支持。不同的应用程序可以有各自独立的虚拟地址空间,它们彼此之间不会相互干扰。当应用程序访问虚拟地址时,操作系统会通过内存管理单元(MMU)将虚拟地址转换为物理地址。
三、MIO简介
ZYNQ MPSOC中包含PS-MIO(Multiplexed I/O)和PS-EMIO(Extended Multiplexed I/O),其中PS - MIO是ZYNQ MPSoc中处理系统的复用输入 / 输出接口。它提供了一种将PS与外部设备直接连接的方式,共有78个MIO引脚。PS-EMIO是PS的扩展复用输入/输出接口。它主要用于扩展ZYNQ MPSoc 的I/O接口能力。当MIO的引脚数量不能满足系统对外部设备连接的需求时,就可以使用EMIO,至多有96个引脚。
四、应用文件
使用GPIO需要设置两个寄存器,一个是设置 GPIO的管脚复用的IOU_SLCR Module寄存器,一个是设置GPIO的管脚功能的GPIO Module寄存器。
在上面给出的ZYNQ MPSOC官方手册中可以找到对应的寄存器地址。
//添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>static int led_major;
static struct class *led_cls;/* gpio 内存映射地址 */
static unsigned long *gpio_addr = NULL;
static unsigned long *iou_slcr = NULL;/* gpio 寄存器物理基地址 */
#define GPIO_BASE_ADDR 0xFF0A0000
#define IOU_SLCR_ADDR 0xFF180000
/* gpio 寄存器所占空间大小 */
#define GPIO_BASE_SIZE 0x368
#define IOU_SLCR_SIZE 0x714/* gpio 引脚设置GPIO功能*/
#define GPIO_PIN_40 (unsigned int *)((unsigned long)iou_slcr + 0x000000A0)
#define GPIO_PIN_42 (unsigned int *)((unsigned long)iou_slcr + 0x000000A8)/* gpio 控制电流0*/
#define IOU_SLCR_BANK1_CTRL0 (unsigned int *)((unsigned long)iou_slcr + 0x00000154)
/* gpio 控制电流1*/
#define IOU_SLCR_BANK1_CTRL1 (unsigned int *)((unsigned long)iou_slcr + 0x00000158)
/* gpio 输入引脚选择Schmitt或CMOS*/
#define IOU_SLCR_BANK1_CTRL3 (unsigned int *)((unsigned long)iou_slcr + 0x0000015C)
/* gpio 设置上下拉*/
#define IOU_SLCR_BANK1_CTRL4 (unsigned int *)((unsigned long)iou_slcr + 0x00000160)
/* gpio 使能上下拉*/
#define IOU_SLCR_BANK1_CTRL5 (unsigned int *)((unsigned long)iou_slcr + 0x00000164)
/* gpio 引脚速率选择*/
#define IOU_SLCR_BANK1_CTRL6 (unsigned int *)((unsigned long)iou_slcr + 0x00000168)/* gpio 方向寄存器 */
#define GPIO_DIRM_1 (unsigned int *)((unsigned long)gpio_addr + 0x00000244)
/* gpio 使能寄存器 */
#define GPIO_OEN_1 (unsigned int *)((unsigned long)gpio_addr + 0x00000248)
/* gpio 输出数据寄存器 */
#define GPIO_DATA_1 (unsigned int *)((unsigned long)gpio_addr + 0x00000044)
/* gpio 输出数据控制寄存器1 */
#define GPIO_MASK_DATA_1_LSW (unsigned int *)((unsigned long)gpio_addr + 0x00000008)
/* gpio 输出数据控制寄存器2 */
#define GPIO_MASK_DATA_1_MSW (unsigned int *)((unsigned long)gpio_addr + 0x0000000C)const struct file_operations led_fops = {};//实现装载入口函数和卸载入口函数
static __init int led_drv_v1_init(void)
{printk("-------^v^-------\n");printk("-led drv v1 init-\n");//申请主设备号led_major = register_chrdev(0, "led_drv_v1", &led_fops);if (led_major < 0){printk("register chrdev faile!\n");return led_major;}printk("register chrdev ok!\n");//创建设备节点//创建设备的类别led_cls = class_create(THIS_MODULE, "led_class");printk("class create ok!\n");//创建设备device_create(led_cls, NULL, MKDEV(led_major, 0), NULL, "Myled%d", 0);printk("device create ok!\n");//1.内存映射gpio_addr = ioremap(GPIO_BASE_ADDR, GPIO_BASE_SIZE);iou_slcr = ioremap(IOU_SLCR_ADDR, IOU_SLCR_SIZE);printk("gpio_addr init over!\n");//2.MIO40 MIO42 设置成GPIO,参考手册设置iowrite32((ioread32(GPIO_PIN_40) & 0x0), GPIO_PIN_40);printk("gpio MIO40 init over!\n");iowrite32((ioread32(GPIO_PIN_42) & 0x0), GPIO_PIN_42);printk("gpio MIO42 init over!\n");// //3.MIO40 MIO42设置输出驱动电流大小// // 将 0x3FFFFFF 和 0x0 转换为二进制。// // 0011 1111 1111 1111 1111 1111 1111// // 0000 0000 0000 0000 0000 0000 0000// // 参考手册,可以知道每一位的地址都是由 CTRL0 和 CTRL1 控制的,在这里都是 10, 查看手册得 8 mA。iowrite32((ioread32(IOU_SLCR_BANK1_CTRL0) | 0x3FFFFFF), IOU_SLCR_BANK1_CTRL0);iowrite32((ioread32(IOU_SLCR_BANK1_CTRL1) & 0x0), IOU_SLCR_BANK1_CTRL1);//4.选择引脚是Schmitt还是CMOSiowrite32((ioread32(IOU_SLCR_BANK1_CTRL3) & 0x0), IOU_SLCR_BANK1_CTRL3);//5.输出管脚上下拉及使能iowrite32((ioread32(IOU_SLCR_BANK1_CTRL4) | 0x3FFFFFF), IOU_SLCR_BANK1_CTRL4);iowrite32((ioread32(IOU_SLCR_BANK1_CTRL5) | 0x3FFFFFF), IOU_SLCR_BANK1_CTRL5);//6.MIO速率的选择iowrite32((ioread32(IOU_SLCR_BANK1_CTRL6) & 0x0), IOU_SLCR_BANK1_CTRL6);//7.MIO40 MIO42 设置成输出// 40 - 26 = 14, 42 - 26 = 16// 0000 0000 000|0 0|000 0000 0000 0000// |16 |14 // 0000 0000 000 1 0 100 0000 0000 0000// 0000 0000 0001 0100 0000 0000 0000// 0x00014000iowrite32((ioread32(GPIO_DIRM_1) | 0x00014000), GPIO_DIRM_1);//MIO40 MIO42 使能iowrite32((ioread32(GPIO_OEN_1) | 0x00014000), GPIO_OEN_1);/* MASK_DATA方式按灭LED1,LED2,这个需要自己修改相关参数 *///iowrite32((ioread32(GPIO_MASK_DATA_0_LSW ) & 0xFEFFEFFF), GPIO_MASK_DATA_0_LSW );//printk("GPIO_MASK_DATA_0_LSW = 0x%x\n", *GPIO_MASK_DATA_0_LSW);//iowrite32((ioread32(GPIO_MASK_DATA_0_MSW ) & 0xFEFFEFFF), GPIO_MASK_DATA_0_MSW );//printk("GPIO_MASK_DATA_0_MSW = 0x%x\n", *GPIO_MASK_DATA_0_MSW);//8.DATA方式按灭LED1,LED2// 1111 1111 1111 111|1 1|111 1111 1111 1111// |0 |0// 1111 1111 1111 1110 1011 1111 1111 1111// 0xFFFEBFFF// iowrite32((ioread32(GPIO_DATA_1) & 0xFFFEBFFF), GPIO_DATA_1);iowrite32((ioread32(GPIO_DATA_1) & 0xFFFEBFFF), GPIO_DATA_1);printk("GPIO_DATA_1 = 0x%x\n", *GPIO_DATA_1);return 0;
}static __exit void led_drv_v1_exit(void)
{iowrite32((ioread32(GPIO_DATA_1) | 0xFFFFFFFF), GPIO_DATA_1);//9.内存释放iounmap(gpio_addr);iounmap(iou_slcr);//删除设备device_destroy(led_cls, MKDEV(led_major, 0));//删除类class_destroy(led_cls);//注销主设备号unregister_chrdev(led_major, "led_drv_v1");printk("-------^v^-------\n");printk("-led drv v1 exit-\n");
}//申明装载入口函数和卸载入口函数
module_init(led_drv_v1_init);
module_exit(led_drv_v1_exit);//添加GPL协议
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Popeye");