Linux简单设备驱动(1):使用IO内存操作GPIO--LED

前言

本人是从ARM7和cortex-Mx系列单片机,向linux转型的初学者。本文主要是总结怎么通过操作芯片的GPIO外设寄存器完成LED的控制,由于本人单片机玩惯了其外设的寄存器,特此来验证利用IO内存将GPIO外设的物理地址映射至IO内存,然后实现GPIO寄存器控制。
本文不涉及,也没考虑到并发和竞态
此文还涉及到chrdev主次设备的申请,这里一起做个总结。
头一次使用CSDN-markdown编辑器,不怎么会用,勿怪。

硬件方面

本文使用的是迅为电子的三星exynos4412处理器开发板,相应的寄存器操作也是基于这款处理器进行开发。

字符设备驱动开发

废话少说进入正题。

1.模块初始化

头文件:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

具体实现代码:

static int __init mchrdev_init(void)
{
    int ret=0;
    ......;
    return ret;
}
static void __exit  mchrdev_exit(void)
{
    ......;
}
module_init(mchrdev_init);
module_exit(mchrdev_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("tarkelc");

2.动态分配设备号

头文件:

#include <linux/types.h>//主次设备
#include <linux/fs.h>//释放和分配设备编号

mchrdev_init()函数中实现代码:

int ret=0;
ret=alloc_chrdev_region(&mdev_t, 0, 1, CHRDEV_NAME);//分配设备号
if(ret!=0){
    printk(KERN_ALERT "alloc_chrdev_region failed. %d \n",ret);
    goto fail1;
}
else{
    printk(KERN_ALERT "alloc_chrdev_region succeed! %d,%d\n",MAJOR(mdev_t),MINOR(mdev_t));
}
.........................................
//字符设备分配内存
mydevice=kmalloc(sizeof(struct scull_dev),GFP_KERNEL);
if(!mydevice){
    printk(KERN_ALERT "kmalloc failed!/n");
    ret=-ENOMEM;
    goto fail2;
}
memset(mydevice,0,sizeof(struct scull_dev));//清空数据
//字符设备注册
setup_cdev(mydevice, 0);
.........................................
printk(KERN_ALERT "mchrdev init!/n");
return ret;
fail2:
    unregister_chrdev_region(mdev_t,1);
fail1:
    return ret;

字符设备注册函数:

static void setup_cdev(struct scull_dev *dev, int index)
{
    int err;
    cdev_init(&dev->cdev,&my_fops);
    dev->cdev.owner=THIS_MODULE;
    dev->cdev.ops=&my_fops;
    err=cdev_add(&dev->cdev,mdev_t,1);
    if(err!=0){
    printk(KERN_EMERG "cdev_add err=%d\n",err);
    }
    else{
        printk(KERN_EMERG "cdev_add succeed!\n");
    }
}

自定义scull_dev结构体,通过它表示每个设备。scull:Simple Character Utility for Loading Localities, 区域装载的简单字符工具,是一个操作内存区域的字符设备驱动程序,这片内存区域就相当于一个设备。

/* 表示每一个设备 */
struct scull_dev {
    char *data;//在file_operations 的read()和write()用上,这里暂时放在这
    unsigned int size;
    struct cdev cdev;//字符设备
};
struct scull_dev *mydevice;

file_operations结构体与应用程序调用有关,这里用不上应用程序,直接先这么写ok

struct file_operations my_fops={
    .owner = THIS_MODULE,   
};

mchrdev_exit()函数中实现代码:

cdev_del(&mydevice->cdev);
............................................
kfree(mydevice);
unregister_chrdev_region(mdev_t,1);
printk(KERN_ALERT "mchrdev exit!/n");

3.注册设备class和生成设备节点

mchrdev_init()函数中实现代码:

..............................................
//注册设备class
myclass=class_create(THIS_MODULE,CLASS_NAME);
..............................................
//生成设备节点
device_create(myclass,NULL,mdev_t,NULL,DEVICE_NAME);

mchrdev_exit()函数中实现代码,如代码所示,更换顺序系统直接GG

..............................................
/*下面这两个释放函数有顺序要求!!*/
device_destroy(myclass,mdev_t);
class_destroy(myclass);
..............................................

class结构体声明,这里貌似只是声明注册而已,其它地方没见用到。。。
在最后insmod加载,通过使用命令“ls /sys/class/”可以查看到生成的class

static struct class *myclass;

4.IO内存对GPIO寄存器进行映射和操作

GPIO物理地址

开发板的LED灯由GPL2_0引脚控制,其物理地址为

#define EXYNOS4_PA_GPIO2        0x11000000
#define GPL2BASE_PA (EXYNOS4_PA_GPIO2+0x0100)
#define GPL2LEN_PA  (0X20)
GPIO相应寄存器

GPL2CON 0x0100 Port group GPL2 configuration register 0x0000_0000
GPL2DAT 0x0104 Port group GPL2 data register 0x00
GPL2PUD 0x0108 Port group GPL2 pull-up/pull-down register 0x5555
GPL2DRV 0x010C Port group GPL2 drive strength control register 0x00_0000
GPL2CONPDN 0x0110 Port group GPL2 power down mode configuration register 0x0000
GPL2PUDPDN 0x0114 Port group GPL2 power down mode pull-up/pull-down register 0x0000
本人用惯了STM32系列单片机的寄存器操作形式,不喜自己定义把

//操作GPIO寄存器结构体
typedef struct{
    unsigned long CON;  //0x00
    unsigned long DAT;  //0x04
    unsigned long PUD;  //0x08
    unsigned long DRV;  //0x0c
    unsigned long CONPND;   //0x10
    unsigned long PUDPDN;   //0x14
    unsigned long RESERVED; //0x18
    unsigned long RESERVED1;    //0x1c
}GPIO_TypeDef;
GPIO_TypeDef *GPL2;
IO内存初始化和操作
void *p1;
/* 自定义函数 */
static void gpio_init(void)
{
unsigned int state;
//GPL2BASE寄存器向IO内存的映射
if(request_mem_region(GPL2BASE_PA,GPL2LEN_PA,DRIVER_NAME)==NULL) {
    goto GPL2BASE_FAIL;
}
else{
    printk(KERN_EMERG "request_mem_region succeed!\n");
}
//GPL2BASE IO内存的重映射
p1=ioremap(GPL2BASE_PA,GPL2LEN_PA);
if(p1==NULL){
    goto GPL2IOMEM_FAIL;
}
else {      
    GPL2=(GPIO_TypeDef *)p1;
    printk(KERN_EMERG "GPL2 ioremap succeed!\n");
}
state=ioread32(&GPL2->CON);
iowrite32(state&0xfffffff1,&GPL2->CON);//配置IO口输出
state=ioread32(&GPL2->DAT);
iowrite32(state|0x00000001,&GPL2->DAT);//配置IO输出高电平
return;
GPL2IOMEM_FAIL:
printk(KERN_EMERG "GPL2 ioremap failed!\n");
release_mem_region(GPL2BASE_PA,GPL2LEN_PA);
GPL2BASE_FAIL:
printk(KERN_EMERG "request_mem_region failed!\n");
return;
}
IO内存的释放
/* 自定义函数 */
static void gpio_deinit(void)
{
iounmap(p1);
release_mem_region(GPL2BASE_PA,GPL2LEN_PA);
}

最后

将编译好的.ko文件,insmod进去就会看见LED亮了。。。。。。
具体代码在本文作者的资源里可以找到。。。。。
先从这里开始再慢慢深入学习把。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值