/********************************
* GPIO驱动程序控制GPIO接口高低电平
* 四个GPIO识别为四个设备
* 创建四个文件分别控制四个GPIO
* echo on|off > /dev/driverx
* 使用电表测量管脚电压观察结果
* 本例内容详见LDD3第三章
* 开发板:Tiny 4412
* 主控芯片:Exynos 4412
* author: zhangn
* date: 2016-1-10
********************************/
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
//定义设备号
#define GPIO_MAJOR 60 //主
#define GPIO_NUM 4 //次
//定义寄存器地址
#define GP_BASE 0X11000000 //设备的物理基地址,ioremap函数要用到
#define GP_SIZE 0X1000 //物理地址映射到内核的空间大小
//寄存器基于物理基地址偏移量
#define GPX3CON 0x0C60
#define GPX3DAT 0x0C64
#define GPX3PUD 0x0c68
//1.为每个char设备准备一个私有结构体
struct mygpio
{
int gpio_num;//设备编号,本例中共有四个设备
int gpio_state;//设备状态:on/1,off/0
dev_t dev_id;//设备号,内核驱动必须有
struct cdev gpio_cdev;//内核内部使用该结构来表示字符设备,内核调用设备操作之前必须分配该结构
};
static struct mygpio *gpios[GPIO_NUM];
static void __iomem *vir_base;//如果映射多次,写再结构体内部
//9.实现file_operations中的函数指针
static int gpio_open(struct inode *inode, struct file *filp)
{
/*应用层每打开一次文件,内核维护一个file结构体
同时为每个打开的文件维护一个inode结构体
一个文件多次打开有多个file,但都指向同一个inode
对于inode结构体,我们这里只关心两个成员
dev_t i_rdev,包含init时注册到系统的设备编号
struct cdev *i_cdev指向一个cdev结构体的指针,一个cdev对应一个设备
在这里就是私有结构体mygpio的成员gpio_cdev的指针
应用层打开一个文件,内核创建一个file结构体
file中通过file_operations结构体讲一组操作文件的函数关联
同时内核维护一个inode,同一文件第一次打开创建,多次打开增加计数
详情请自行查阅vfs相关资料
内核调用gpio_open函数时,通过inode参数将设备信息传进来
同时将每次打开文件的file的信息传进来
这里详见ldd3第三章
*/
struct mygpio *tmp = container_of(inode->i_cdev, struct mygpio, gpio_cdev);
filp->private_data = tmp;//该成员主要用来在不同函数中通过file传递数据
return 0;
}
static int gpio_release(struct inode *inode, struct file *filp)
{
//如果open中有需要释放的资源在这里释放
//open中的tmp指向的内存在外面分配和释放,这里不需要释放
return 0;
}
static ssize_t gpio_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct mygpio *dev = filp->private_data;
char tmp[10] = {0};
int value;
if(copy_from_user(tmp, buf, 3))
return -EFAULT;
if(strncmp(tmp, "on", 2) == 0)
{
value = readl(vir_base + GPX3DAT);
value &= ~(1 << (dev->gpio_num+2));
writel(value, vir_base + GPX3DAT);
dev->gpio_state = 1;
}
else if(strncmp(tmp, "off", 3) == 0)
{
value = readl(vir_base + GPX3DAT);
value |= 1 << (dev->gpio_num + 2);
writel(value, vir_base + GPX3DAT);
dev->gpio_state = 0;
}
else
return -1;
return count;
}
//2.准备file_operations结构体
static struct file_operations gpio_fops =
{
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_release,
.write = gpio_write,
};
static int __init my_init(void)
{
int i, value;
//3.物理地址映射到内核虚拟地址
vir_base = ioremap(GP_BASE, GP_SIZE);
if(!vir_base)
{
printk("Cannot ioremap\n");
return -EIO;
}
//以下工作每个设备完成一次
for(i = 0; i < GPIO_NUM; ++i)
{
//4.为每个设备的私有结构体分配内存
gpios[i] = kzalloc(sizeof(*gpios[i]), GFP_KERNEL);
if(!gpios[i])
{
//为已分配成功的结构体释放内存
for(; i >= 0; --i)
kfree(gpios[i]);
//释放地址映射
iounmap(vir_base);
return -ENOMEM;
}
//5.为每个设备初始化寄存器
value = readl(vir_base + GPX3CON);
value |= 0x11111111;
writel(value, vir_base + GPX3CON);
value = readl(vir_base + GPX3DAT);
value |= 1 << (i+2);//使用寄存器的第2345号管脚
writel(value, vir_base + GPX3DAT);
value = readl(vir_base + GPX3PUD);
value = 0x3;
writel(value, vir_base + GPX3PUD);
gpios[i]->gpio_num = i;
gpios[i]->gpio_state = 0;
//6.为设备分配主次设备号
gpios[i]->dev_id = MKDEV(GPIO_MAJOR, i);
//7.初始化cdev结构体,并和file_operations结构体关联
cdev_init(&gpios[i]->gpio_cdev, &gpio_fops);
gpios[i]->gpio_cdev.owner = THIS_MODULE;
gpios[i]->gpio_cdev.ops = &gpio_fops;
//8.通过cdev_add将mygpio中的设备号添加到cdev结构体中
cdev_add(&gpios[i]->gpio_cdev, gpios[i]->dev_id, 1);
}
return 0;
}
static void __exit my_exit(void)
{
int i;
iounmap(vir_base);
for(i = 0; i < GPIO_NUM; ++i)
{
if(gpios[i])
{
cdev_del(&gpios[i]->gpio_cdev);
kfree(gpios[i]);
}
}
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangN");