驱动学习5: zynq实现点亮led

本文介绍了一个AXI GPIO设备驱动的实现过程,包括内核模块的初始化、设备注册、文件操作函数的实现等内容,并提供了一段完整的驱动代码及应用层示例。该驱动实现了LED灯的开关控制功能。

 

驱动代码:

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <asm/irq.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>            //包含了device、class 等结构的定义
#include <asm/io.h>               //包含了ioremap、iowrite等内核访问IO内存等函数
#include <linux/uaccess.h>        //包含了copy_to_user、copy_from_user等
   
#define DEVICE_NAME          "axiled"
#define CLASS_NAME           "zynqebb" 

/* XGPIO Physical address */
#define XGPIO_PHY_ADDR 0x41200000      //This Address is based SDK 
/* Register Offset Definitions */
#define XGPIO_DATA_OFFSET   (0x0)    /* Data register  */
#define XGPIO_TRI_OFFSET    (0x4)    /* I/O direction register  */

volatile unsigned long *Gpio_DIR = NULL;
volatile unsigned long *Gpio_DATA = NULL;
  

MODULE_AUTHOR("Xilinx ");  
MODULE_DESCRIPTION("AXI GPIO moudle dirver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL");  
  
static int axi_gpio_driver_major;  
static struct class* axi_gpio_driver_class = NULL;  
static struct device* axi_gpio_driver_device = NULL;  
  
unsigned long axi_gpio_virt_addr = 0;        //AXI_GPIO moulde's  visual address  
  
static ssize_t axi_gpio_write(struct file * file, const char * buf, size_t count,loff_t *off)
{
    //printk("axi_gpio_write\n");
    int  val;
    copy_from_user(&val,buf,count);
    printk("before Gpio_DATA:%lx,%lx.\n",Gpio_DATA,ioread32(Gpio_DATA));
    
    if(val == 1)
    {
        //点灯
        //*Gpio_DATA = (unsigned long )0x00000005;     //设置AXI GPIO的方向输出全为高  
        iowrite32(0x0000000F,Gpio_DATA); //设置AXI GPIO的方向输出全为高  
    }
    else
    { 
        //灭灯 
        iowrite32(0x00000000,Gpio_DATA); //设置AXI GPIO的方向输出全为低  
    }
    printk("after Gpio_DATA:%lx,%lx.\n",Gpio_DATA,ioread32(Gpio_DATA));
    return 0;

}

static int axi_gpio_open(struct inode *inode, struct file *filp)
{
    //printk("axi_gpio_open\n");

    //配置led管脚为输出
    printk("before Gpio_DIR:%lx,%lx.\n",Gpio_DIR,ioread32(Gpio_DIR));
    //*Gpio_DIR = (unsigned long )0x00000000;       //设置AXI GPIO的方向输出  
    iowrite32(0x00000000,Gpio_DIR);  //设置AXI GPIO的方向输出  
    
    printk("after Gpio_DIR:%lx,%lx.\n",Gpio_DIR,ioread32(Gpio_DIR));
    
    return 0;
}
  
static struct file_operations axi_gpio_fops = {  
    .owner = THIS_MODULE,  
    .write       = axi_gpio_write,
    .open        = axi_gpio_open,
};  


static int __init axi_gpio_driver_module_init(void)  
{  
    int ret;  
  
    axi_gpio_driver_major=register_chrdev(0, DEVICE_NAME, &axi_gpio_fops );//内核注册设备驱动  
    if (axi_gpio_driver_major < 0){  
        printk("failed to register device.\n");  
        return -1;  
    }  
  
    axi_gpio_driver_class = class_create(THIS_MODULE, CLASS_NAME);//创建设备类  
    if (IS_ERR(axi_gpio_driver_class)){  
        printk("failed to create zxi_gpio moudle class.\n");  
        unregister_chrdev(axi_gpio_driver_major, DEVICE_NAME);  
        return -1;  
    }  
  
  
    axi_gpio_driver_device = device_create(axi_gpio_driver_class, NULL, MKDEV(axi_gpio_driver_major, 0), NULL, DEVICE_NAME);   
    if (IS_ERR(axi_gpio_driver_device)){  
        printk("failed to create device .\n");  
        unregister_chrdev(axi_gpio_driver_major, DEVICE_NAME);  
        return -1;  
    }  
     
     
    //To get Custom IP--gpio moudle's virtual address 
#if 1    
    if(request_mem_region(XGPIO_PHY_ADDR, 0x1000,DEVICE_NAME) == NULL ){
        printk( "request_mem_region failed\n");
        return -1;
    }
#endif  
    axi_gpio_virt_addr = (unsigned long)ioremap(XGPIO_PHY_ADDR, sizeof(u32));
    //将模块的物理地址映射到虚拟地址上  
    printk( "ioremap called: phys %#08x -> virt %#08x\n",XGPIO_PHY_ADDR, axi_gpio_virt_addr );
    //指定需要操作的寄存器的地址
    Gpio_DIR =  (unsigned long *)(axi_gpio_virt_addr + XGPIO_TRI_OFFSET);
    Gpio_DATA = (unsigned long *)(axi_gpio_virt_addr + XGPIO_DATA_OFFSET);
    
    return 0;
}  
static void __exit axi_gpio_driver_module_exit(void)  
{  

    //撤销映射关系 
    iounmap((void *)axi_gpio_virt_addr); 
#if 1
    release_mem_region(XGPIO_PHY_ADDR, 0x1000);
#endif
    device_destroy(axi_gpio_driver_class, MKDEV(axi_gpio_driver_major, 0));  
    class_unregister(axi_gpio_driver_class);  
    class_destroy(axi_gpio_driver_class);  
    unregister_chrdev(axi_gpio_driver_major, DEVICE_NAME);  
    printk("axi_gpio module exit.\n");  
}  
  
module_init(axi_gpio_driver_module_init);  
module_exit(axi_gpio_driver_module_exit);  

应用层代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

/*  firstdrvtest on
  * firstdrvtest off
  */
int main(int argc, char **argv)
{
    int fd;
    int val = 1;
    fd = open("/dev/axiled", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
    }
    if (argc != 2)
    {
        printf("Usage :\n");
        printf("%s <on|off>\n", argv[0]);
        return 0;
    }

    if (strcmp(argv[1], "on") == 0)
    {
        val  = 1;
    }
    else
    {
        val = 0;
    }
    
    write(fd, &val, 4);
    return 0;
}

 

插入模块:

root@plnx_arm:/mnt# insmod mytest.ko
ioremap called: phys 0x41200000 -> virt 0xf09f0000

测试设备号:

运行应用程序:

 

转载于:https://www.cnblogs.com/shuqingstudy/p/9175189.html

### 字符设备驱动开发概述 在Zynq 7010平台上实现LED点亮的字符设备驱动示例,需要结合Linux内核模块机制和设备树配置。通过字符设备驱动程序,可以实现对GPIO的控制,从而点亮或熄灭LED。 #### 设备树配置 为了正确映射硬件资源,在设备树中需要定义一个节点来描述LED设备。以下是一个示例设备树片段: ```dts /include/ "system-conf.dtsi" / { amba_pl: amba_pl { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; ranges; leds: leds@41200000 { compatible = "hello,leds"; reg = <0x41200000 0x1>; }; }; }; ``` 该配置定义了一个位于地址`0x41200000`的LED设备,并指定了其长度为`0x1`字节。此信息将被驱动程序用来访问相应的内存区域[^3]。 #### 字符设备驱动代码 接下来是具体的字符设备驱动程序代码。这段代码实现了基本的字符设备操作,包括打开、读取、写入以及释放设备的功能。 ```c #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/of.h> #include <linux/io.h> MODULE_LICENSE("Dual BSD/GPL"); static dev_t dev_num; static struct cdev char_dev; static void __iomem *regs_base; static int char_dev_open(struct inode *inode, struct file *file) { printk(KERN_INFO "Character device opened\n"); return 0; } static ssize_t char_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { // 实现读取功能 return 0; } static ssize_t char_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { // 假设写入的数据直接控制LED状态 u8 value; if (copy_from_user(&value, buf, sizeof(value))) return -EFAULT; iowrite8(value, regs_base); // 将数据写入寄存器 return count; } static int char_dev_release(struct inode *inode, struct file *file) { printk(KERN_INFO "Character device released\n"); return 0; } static const struct of_device_id my_led_of_match[] = { { .compatible = "hello,leds" }, {}, }; MODULE_DEVICE_TABLE(of, my_led_of_match); static int __init hello_init(void) { struct device_node *np; int ret; alloc_chrdev_region(&dev_num, 0, 1, "char_dev"); cdev_init(&char_dev, &fops); cdev_add(&char_dev, dev_num, 1); printk(KERN_INFO "Module init complete!\nHello, Linux Driver!\n"); np = of_find_matching_node(NULL, my_led_of_match); if (!np) { printk(KERN_ERR "Failed to find LED device node\n"); return -ENODEV; } regs_base = of_iomap(np, 0); if (!regs_base) { printk(KERN_ERR "Failed to map LED registers\n"); return -ENOMEM; } return 0; } static void __exit hello_exit(void) { iounmap(regs_base); unregister_chrdev_region(dev_num, 1); cdev_del(&char_dev); printk(KERN_INFO "Module exit!\nBye, Linux Driver!\n"); } static struct file_operations fops = { .owner = THIS_MODULE, .open = char_dev_open, .read = char_dev_read, .write = char_dev_write, .release = char_dev_release, }; module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("LED Character Device Driver for Zynq 7010"); MODULE_ALIAS("LED driver example"); ``` 上述代码首先分配了字符设备编号,并初始化了字符设备结构体。然后它查找与驱动匹配的设备树节点并进行内存映射,以便能够直接访问硬件寄存器。当用户空间程序向设备写入数据时,这些数据会被发送到指定的寄存器地址,进而改变LED的状态。 #### 编译与部署 完成驱动编写后,需将其添加至Petalinux项目中的适当位置,并更新Kconfig文件以允许在编译时选择该驱动。之后,使用如下命令构建整个系统镜像: ```bash petalinux-build ``` 构建完成后,生成的镜像可以被烧录到目标板上进行测试。 #### 测试驱动 启动目标板后,加载驱动模块并检查是否能够成功注册字符设备。可以通过查看`/proc/devices`文件确认设备号,并创建相应的设备节点。最后,尝试对设备进行读写操作以验证其功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值