前言
本人是从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亮了。。。。。。
具体代码在本文作者的资源里可以找到。。。。。
先从这里开始再慢慢深入学习把。。。。。