分类: LINUX
2012-07-11 11:17:44
GPIO驱动大家都很熟悉,这里就不再说了,那什么是TimedGPIO驱动呢?从名字可以看出来,该驱动和时间有很大关系。不错,TimedGPIO就是将普通的GPIO驱动和内核定时器进行了绑定,从而实现了一个时钟GPIO,即受时钟控制的GPIO。
Timed GPIO的基本原理
与switch驱动一样,Timed GPIO驱动被实现为platform driver,因此初始化时同样会先执行probe函数,然后在sysfs中创建相应的设备文件和class文件,并在/dev目录下创建节点。应用程序可以通过设备文件实现与内核驱动的交互。比如,设置GPIO引脚的输出电平,设置关联内核定时器的过期时间等。 Timed GPIO的实现代码位于drivers/staging/android目录下,主要包括以下几个文件
-timed_gpio.c
-timed_gpio.h
-timed_output.c
-timed_output.h
涉及的文件数量比前面的一些驱动多了
但是其结构框架和实现过程却比其他驱动简单。从原理上已经可以大致看出其实现方式,但我们还是需要对其实现细节进行系统的分析。
Timed GPIO驱动的实现
通过对前面几个驱动的分析,我们已经大概清楚实现一个驱动需要哪些数据了,一般主要包括最基础的设备结构体、设备数据结构体和platformdata等。当然,不是每个驱动都必须有这些结构体,这需要大家的不断积累和总结。TimedGPIO驱动则基本上具备了我们所说的这些结构体,我们首先来分析设备结构体。该驱动的设备结构体timed_output_dev定义于timed_output.h中,代码如下所示:
struct timed_output_dev {
const char *name;
void (*enable)(structtimed_output_dev *sdev, int timeout);
int (*get_time)(structtimed_output_dev *sdev);
struct device *dev;
int state;
}
该结构体表示一个TimedGPIO设备。首先,name代表对应的TimedGPIO设备的名称,因为在linux2.6的设备模型中需要这个name来为设备找到匹配的驱动。接下来是两个函数指针,enabled用来设置定时器的过期时间,并使GPIO能输出指定电平(在timed_GPIO中,它最终是指向gpio_enable);get_time函数则用于获得定时器的剩余时间,即离过期还有多长时间(在timed_GPIO中,它最终是指gpio_get_time)。Dev指向该TimedGPIO设备的设备结构体(structdeivce)对象,在linux2.6中,内核设备模型把每个设备用一个structdevice来表示。index则是区分同一名称的多个设备(这些设备 由丗 一个驱动管理)
state则表示设备的当前状态。下面继续分析timed_output的实现。timed_output的初始化过程如下:
static intcreate_timed_output_class(void)
{
if (!timed_output_class) {
timed_output_class =class_create(THIS_MODULE, “timed_output”);
if (IS_ERR(timed_output_class))
return PTR_ERR(timed_output_class);
atomic_set(&device_count, 0);
}
return 0;
}
static int __inittimed_output_init(void)
{
return create_timed_output_class();
}
static void __exittimed_output_exit(void)
{
class_destroy(timed_output_class);
}
module_init(timed_output_init);
module_exit(timed_output_exit);
经过对前面一些驱动的分析,相信这部分内容大家已经很熟悉了,所以这里只讲解一下过程,具体原理大家可以参考前面所讲的驱动的初始化操作:初始化时执行timed_output_init函数,然后调用create_timed_output_class创建一个timedoutput设备类;退出则执行timed_output_exit函数,直接销毁所创建的timedoutput类。
我们需要在该类中提供负责具体设备的注册和缷载的函数,它们分别是timed_output_dev_register和timed_output_dev_unregister。下面我们主要来看注册操作的过程,实现如下:
int timed_output_dev_register(structtimed_output_dev *tdev)
{
int ret;
if (!tdev || !tdev->name ||!tdev->enable || !tdev->get_time)
return -EINVAL;
//创建timedoutput类
ret = created_timed_output_class();
if (ret < 0)
return ret;
//保存索引
tdev->index =atomic_inc_return(&device_count);
//创建设备
tdev->dev =device_create(timed_output_class, NULL, MKDEV(0, tdev->index),NULL, tdev->name);
if (IS_ERR(tdev->dev))
return PTR_ERR(tdev->dev);
//创建设备文件
ret = device_create_file(tdev->dev,&dev_attr_anable);
if (ret < 0)
goto err_create_file;
//设置tdev->dev->driver_data数据
dev_set_drvdata(tdev->dev, tdev);
tdev->state = 0;
return 0;
//错误处理
err_create_file:
device_destroy(timed_output_class,MKDEV(0, tdev->index));
printk(KERN_ERR “timed_output:Failed to register driver %s\n”, tdev->name);
return ret;
}
EXPORT_SYMBOL_GPL(timed_output_dev_register);
注册流程:先创建timedoutput类并保存索引,然后创建具体设备,最后创建设备文件并设置设备的driver_data数据。与之对应的缷载函数timed_output_dev_unregister大家就更熟悉了,
首先通过device_remove_file删除设备文件,然后调用device_destroy销毁设备类,最后将设备的driver_data数据设置为NULL。还需要实现show和store两个函数以供应应用层调用,它们的实现过程很简单。
下面来分析设备timed_gpio的具体实现,它将与定时器绑定和关联。首先,同样来分析一下其所需要用到的结构体,定时器的绑定会用到timed_gpio_data结构体,其定义如下:
struct timed_gpio_data {
struct timed_output_dev dev;
struct hrtimer timer;
spinlock_t lock;
unsigned gpio;
int max_timeout;
u8 active_low;
}
该结构体实现了普通的GPIO与定时器的绑定,其中dev就不必再多说了,timer是我们绑定的一个hrtimer定时器,lock是一个自旋锁,gpio表示对应的GPIO,max_timeout表示最大的timerout时间。active_low的功能比较多,在初始化(probe)阶段,会根据这个变量设置GPIO输出的电平,此时该变量的作用类似于指定GPIO在初始化时的默认电平。但是,在通过sysfs的enable函数设置GPIO输出电平时,这个变量则作为一个标志使用。如果active_low不等于0,则将输出电平极性反转,否则不反转,后面的具体操作中我们还会对其进行详细介绍。
现在我们就给大家介绍timed_gpio结构体,它的实现也非常简单,而且timed_gpio_data颇有一些相同之处,它表示一个具体的timed_gpio,定义如下:
struct timed_gpio {
const char *name;
unsigned gpio;
int max_timeout;
u8 active_low;
};
其中name表示GPIO的名称,gpio表示对应的GPIO,max_timeout表示最大超时时间,active_low与timed_gpio_data->active_low的功能一样。该结构体只是表示一个单独的GPIO,我们还需要一个结构体来表示一组GPIO,该结构体定义如下:
struct timed_gpio_platform_data {
int num_gpios;
struct timed_gpio *gpios;
};
该结构体的定义只包括了GPIO的数量(numgpios)和具体timed_gpio设备的结构体。这些GPIO由同一个TimedGPIO驱动管理。下面我们来分析一个具体的timedgpio的实现。
因为TimedGPIO是基于timed_gpio的,所以实现非常简单。它基于timed_output.c提供的功能,实现了一个基于platformdriver构架的驱动,提供了也是标准的接口。其init函数和exit函数分别调用platform_driver_register和platform_driver_unregister来注册/注销timed_gpio_driver,具体实现请参见源代码。
timed_gpio_driver驱动的定义如下:
static struct platform_drivertimed_gpio_driver = {
.probe = timed_gpio_probe,
.remove = timed_gpio_remove,
.driver = {
.name = TIMED_GPIO_NAME,
.owner = THIS_MODULE,
},
};
该结构体中指明了初始化(probe)函数timed_gpio_probe和移除(remove)函数timed_gpio_remove,以及驱动的名称和模块,下面我们主要来分析一下初始化函数和移除函数的具体实现。
static int timed_gpio_probe(structplatform_device *pdev)
{
struct timed_gpio_platform_data*pdata = pdev->dev.platform_data;
struct timed_gpio *cur_gpio;
struct timed_gpio_data *gpio_data,*gpio_dat;
int I, j, ret = 0;
if (!pdata)
return -EBUSY;
//分配pdata->num_gpios个GPIO对象空间
gpio_data = kzalloc(sizeof(structtimed_gpio_data) * pdata->num_gpios, GFP_KERNEL);
if (!gpio_data)
return -ENOMEM;
for (i=0; i
cur_gpio = &pdata->gpios[i];
gpio_dat = &gpio_data[i];
//初始化GPIO的定时器
hrtimer_init(&gpio_dat->timer,CLOCK_MONOTONIC, HRTIMER_MODE_REL);
//设置超时后的回调函数
gpio_dat->timer.function =gpio_timer_func;
spin_lock_init(&gpio_dat->lock);
//初始化GPIO
gpio_dat->dev.name =cur_gpio->name;
//设置get_time为gpio_get_time
gpio_dat->dev.get_time =gpio_get_time;
//设置enable为gpio_enable
gpio_dat->dev.enable =gpio_enable;
//注册设备
ret =timed_output_dev_register(&gpio_dat->dev);
if (ret < 0)
for (j=0; j
timed_output_dev_unregister(&gpio_data[i].dev);
kfree(gpio_data);
return ret;
}
//设置GPIO和超时时间电平等
gpio_dat->gpio = cur_gpio->gpio;
gpio_dat->max_timeout =cur_gpio->max_timeout;
gpio_dat->active_low =cur_gpio->active_low;
//设置其初始输出电平
gpio_direction_output(gpio_dat->gpio,gpio_dat->active_low);
}
//设置设备驱动的私有数据
platform_set_drvdata(pdev,gpio_data);
return 0;
}
初始化过程中,首先分配pdata->num_gpios个GPIO对象空间,因为我们说过可能会存在一组GPIO设备,所以这里我们创建了指定个数的GPIO,然后对每个设备执行初始化操作,主要包括以下步骤:
1)调用httimer_init初始化内核定时器。
2)指定定时器超时后的回调函数gpio_timer_func;
3)设置GPIO的enable函数为gpio_enable;
4)设置GPIO的get_time函数为gpio_get_time;
5)调用timed_output_dev_register注册设备驱动(创建sysfs设备文件,创建structdevice对象);
6)初始化其他成员变量(GPIO、超时时间、电平);
7)调用gpio_direction_output设置其初始输出电平;
8)调用platform_set_drvdata设置设备驱动的私有数据。
下面我们来分析一下定时器超时之后的回调函数gpio_timer_func的实现,它的定义如下:
static enum hrtimer_restartgpio_timer_func(struct hrtimer *timer)
{
struct timed_gpio_data *data =container_of (timer, struct timed_gpio_data, timer);
gpio_direction_output(data->gpio,data->active_low ? 1: 0);
return HRTIMER_NORESTART;
}
该回调函数主要完成一个功能,根据active_low来判断输出的电平是高电平还是低电平。如果active_low!= 0, 则输出高电平;如果active_low = 0,则输出低电平。
我们再来分析移除函数timed_gpio_remove执行了哪些操作,其定义如下:
static int timed_gpio_remove(structplatform_device *pdev)
{
//取得私有数据使用权。
struct timed_gpio_platform_data*pdata = pdev->dev.platform_data;
struct timed_gpio_data *gpio_data =platform_get_drvdata(pdev);
int I;
for (I = 0; i
timed_output_dev_unregister(&gpio_data[i].dev);
//释放数据
kfree(gpio_data);
return 0;
}
该函数会首先取得私有数据的使用权,这里需要注意的是,我们创建了num_gpios个GPIO,就需要分别对每一个GPIO调用timed_output_dev_unregister来执行缷载操作,最后则是释放掉timed_gpio_data数据。
最后,还需要实现enable和get_time对应的函数gpio_enable和gpio_get_time,其中gpio_get_time的实现很简单,直接调用hrtimer_get_remaining函数取得其定时器剩下的时间并返回即可。这里我们主要来分析一下gpio_enable的实现,其定义如下:
static void gpio_enable(structtimed_output_dev *dev, int value)
{
struct timed_gpio_data *data =container_of(dev, struct timed_gpio_data, dev);
unsigned long flags;
spin_lock_irqsave(&data->lock,flags);
hrtimer_cancel(&data->timer);
//设置输出电平
gpio_direction_output(data->gpio,data->active_low ? !value : !!value);
if (value > 0) {
if (value > data->max_timeout)
value = data->max_timeout;
//启动定时器timer
hrtimer_start(&data->timer,ktime_set(value / 1000, (value % 1000) * 1000000), HRTIMER_MODE_REL);
}
spin_unlock_irqrestore(&data->lock,flags);
}
首先,我们需要明确TimedGPIO关联的定时器(hrtimer)并不是周期性的,因此每次都需要调用gpio_enable函数启动。当超时后,内核就自动调用其回调函数gpio_timer_func。此后,除非再次调用gpio_enable函数来启动定时器,否则不会在触发超时之后去调用回调函数gpio_timer_func。因此,在gpio_enable函数中,首先应根据参数value输出GPIO的电平,然后再用value参数重置定时器,(hrtimer),并重新启动它。于是,在value指定的超时时间到期后,就会再次触发回调函数gpio_timer_func。程序在通过sysfs的enable函数设置GPIO输出电平后,如果value不等于0,则输出高电平,否则输出低电平。