#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/slab.h>
/*-----------------------------------------------------*/
#define DEVICE_NAME "kio"
#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
/*------------------------------------------------------*/
struct gpio_qset;
/*設備編號存儲結構體*/
struct dev_num
{
dev_t devNum;
unsigned int major;
unsigned int minor;
unsigned int minor_first;
unsigned int count;
};
struct dev_num gpio_dev_num;
/*設備描述結構體*/
struct gpio_dev
{
struct cdev cdev;
struct gpio_qset* dptr; //設備數據存儲鏈表第一項
unsigned long size; //鏈表長度(隨着申請的GPIO端口數增長)
};
struct gpio_dev *gpio_devp;
/*設備數據存儲結構體(采用鏈式存儲,不理解的可以看《數據結構》)*/
struct gpio_qset
{
unsigned long port; //端口號 #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
unsigned int ddr; //方向(輸入(0)或輸出(1))
char value; //高(1)低(0)電平
unsigned long num; //當前編號(按照申請順序編號)
char label[10]; //申請的GPIO使用名稱
struct gpio_qset* next; //指向鏈表下一項的指針
};
/**
* 功能:初始化gpio_dev
* *inode:
* *filp:
* 描述:用戶空間調用open時運行
* int (*open) (struct inode *, struct file *);
* 返回值:0
*/
static int gpio_open(struct inode *inode, struct file *filp)
{
struct gpio_dev *dev;
//由container_of獲得結構體指針inode中結構體cdev的指針,
//讓結構體指針dev指向上述的指針所指的地址,
//再讓file->private指向這一地址,
dev = container_of(inode->i_cdev, struct gpio_dev, cdev);
filp->private_data = dev;
dev->dptr = NULL;
dev->size = 0;
//printk(KERN_ERR "gpio_open success!\n");
return 0;
}
/**
* 功能:釋放申請的GPIO端口以及釋放鏈表(gpio_qset)
* *inode:
* *filp:
* 描述:用戶空間調用close時運行
* int (*release) (struct inode *, struct file *);
* 返回值:0
*/
static int gpio_close(struct inode *inode, struct file *filp)
{
struct gpio_dev *dev = filp->private_data;
struct gpio_qset *qset, *qsetTmp;
qsetTmp = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
for(qset = dev->dptr; qset->next !=NULL; qset=qsetTmp)
{
qsetTmp = qset->next;
gpio_free(qset->port); //釋放申請的端口
kfree(qset); //釋放gpio_qset內存
}
gpio_free(qsetTmp->port);
kfree(qsetTmp);
//printk(KERN_ERR "gpio release!\n");
return 0;
}
/**
* 功能:申請新的gpio_qset的內存,確定相應的GPIO端口功能
* *inode:
* *filp:
* cmd:實現的功能,
* 0:讀,
* 1:寫,
* 2:釋放GPIO端口
* arg:執行操作的GPIO端口
* 描述:用戶空間調用ioctl時運行
* long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
* 返回值:成功返回操作的GPIO端口號
* 錯誤返回相應的錯誤碼
*/
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct gpio_dev *dev = filp->private_data;
struct gpio_qset *qset;
//cmd == 2 設計成為釋放arg端口的功能,之后申請內存的任務便不必執行了,所以直接返回
if(cmd == 2)
{
gpio_free(arg);
return arg;
}
dev->size++;
if(dev->dptr == NULL)
{
dev->dptr = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
qset = dev->dptr;
}
else
{
for(qset = dev->dptr; qset->next != NULL; qset = qset->next); //找到鏈表最后一項
qset->next = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
qset = qset->next;
}
/*鏈表數據*/
qset->num = dev->size; //確定自己的編號
qset->ddr = cmd; //確定方向
qset->port = arg; //確定端口號
qset->next = NULL; //最后一項地址清空
//printk(KERN_ERR "qset->num=%d,qset->ddr=%d,qset->port=%d\n", qset->num, qset->ddr, qset->port);
sprintf(qset->label, "gpio%ld", qset->port); //確定申請的GPIO使用名稱(和端口直接相關)
ret = gpio_request(qset->port, qset->label); //申請端口
/*由於gpio_requset會自己判斷成功與否並且退出函數,故注釋掉對ret的判斷
if(ret < 0)
printk(KERN_ERR "%s_requset failled!%d \n", qset->label, ret);
*/
/*判斷GPIO工作方向(輸出或輸出)*/
switch(qset->ddr)
{
case 0:
ret = gpio_direction_input(qset->port);
if(ret < 0)
printk(KERN_ERR "gpio_direction_input failled!\n");
break;
case 1:
ret = gpio_direction_output(qset->port, 1);
if(ret < 0)
printk(KERN_ERR "gpio_direction_output failled!\n");
break;
default:
return -EPERM; /* Operation not permitted */
}
return qset->num;
}
/**
* 功能:獲取相應端口電平,寫入相應的qset_dev->value,寫到用戶空間的readBuf
* *inode:
* *filp:
* *readBuf:讀數據緩存指針,用戶空間的指針
* port:被讀取的GPIO端口號
* *offp:
* 描述:用戶空間調用read時運行
* ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
* 返回值:成功返回qset->value
* 錯誤返回相應的錯誤碼
*/
static ssize_t gpio_read (struct file *filp, char __user *readBuf, size_t port, loff_t *offp)
{
long ret;
struct gpio_dev *dev = filp->private_data;
struct gpio_qset *qset;
if(dev->dptr == NULL)
return -ENODEV; /* No such device */
for(qset = dev->dptr; qset != NULL; qset = qset->next)
{
if(qset->port == port)
break;
if(qset->next == NULL)
return -ENODEV; /* No such device */
}
if(qset->ddr != 0) //判斷是否ioctl設置為讀操作
return -EPERM; /* Operation not permitted */
qset->value = gpio_get_value(qset->port);
//printk(KERN_ERR "qset->port:%d, qset->value:%d\n", qset->port, qset->value);
switch(qset->value)
{
case 0:
ret = copy_to_user(readBuf, "0", 1);
break;
case 1:
ret = copy_to_user(readBuf, "1", 1);
break;
}
return qset->value;
}
/**
* 功能:寫入相應端口電平,寫入相應的qset_dev->value,數據來自用戶空間的writeBuf
* *inode:
* *filp:
* *writeBuf:寫數據緩存指針,用戶空間的指針
* port:被寫入的GPIO端口號
* *offp:
* 描述:用戶空間調用write時運行
* ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
* 返回值:成功返回qset->value
* 錯誤返回相應的錯誤碼
*/
static ssize_t gpio_write (struct file *filp, const char __user *writeBuf, size_t port, loff_t *offp)
{
long ret;
struct gpio_dev *dev = filp->private_data;
struct gpio_qset *qset;
if(dev->dptr == NULL)
return -ENODEV; /* No such device */
for(qset = dev->dptr; qset != NULL; qset = qset->next)
{
// printk(KERN_ERR "qset->port=%d,port=%d\n", qset->port, port);
if(qset->port == port)
break;
if(qset->next == NULL)
return -ENODEV; /* No such device */
}
if(qset->ddr != 1) //判斷是否ioctl設置為寫操作
return -EPERM; /* Operation not permitted */
ret = copy_from_user(&qset->value, writeBuf, 1);
//printk(KERN_ERR "write:%d\n", qset->value);
switch(qset->value)
{
case '0': qset->value = 0;
gpio_set_value(qset->port, 0);
break;
default : qset->value = 1;
gpio_set_value(qset->port, 1);
break;
}
return qset->value;
}
/*文件操作結構體*/
static const struct file_operations gpio_fops = {
.owner = THIS_MODULE,
.open = gpio_open,
.unlocked_ioctl = gpio_ioctl,
.write = gpio_write,
.read = gpio_read,
.release = gpio_close,
};
/*cdev注冊函數*/
static void gpio_setup_cdev(struct gpio_dev *dev, int index)
{
int ret,devno = gpio_dev_num.devNum + index;
cdev_init(&dev->cdev, &gpio_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &gpio_fops;
ret = cdev_add(&dev->cdev, devno, 1);
if(ret)
printk(KERN_ERR "error %d : adding gpioCtl%d",ret,index);
}
static struct class *kio_class;
/*設備初始化函數*/
static int __init omap2gpio_init(void)
{
int ret;
gpio_dev_num.count = 1;
gpio_dev_num.minor_first = 0;
ret = alloc_chrdev_region(&gpio_dev_num.devNum,
gpio_dev_num.minor_first,
gpio_dev_num.count,
DEVICE_NAME);
if(ret < 0)
return ret;
gpio_dev_num.major = MAJOR(gpio_dev_num.devNum);
gpio_dev_num.minor = MINOR(gpio_dev_num.devNum);
gpio_devp = kzalloc(sizeof(struct gpio_dev),GFP_KERNEL);
gpio_setup_cdev(gpio_devp, 0);
printk(KERN_ERR "gpio alloc_chrdev_region success, major = %d\n",
gpio_dev_num.major);
kio_class = class_create(THIS_MODULE, DEVICE_NAME);
device_create(kio_class, //pointer to the struct class that this device should be registered to
NULL,
gpio_dev_num.devNum, //the dev_t for the char device to be added
NULL,
"%s",
DEVICE_NAME);
return 0;
}
/*設備釋放函數*/
static void __exit omap2gpio_exit(void)
{
cdev_del(&gpio_devp->cdev);
kfree(gpio_devp);
unregister_chrdev_region(gpio_dev_num.devNum, 1);
device_destroy(kio_class, gpio_dev_num.devNum); //the dev_t for the char device to be added
class_destroy(kio_class);
printk(KERN_ERR "gpio unregister_chrdev_region success, major = %d\n",
gpio_dev_num.major);
}
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Mlo_Lv,Tute-421E-studio");
MODULE_DESCRIPTION("This module is used to conrtol ti omap2 gpio");
module_init(omap2gpio_init);
module_exit(omap2gpio_exit);