Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3010146
  • 博文数量: 674
  • 博客积分: 17881
  • 博客等级: 上将
  • 技术积分: 4849
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-17 10:15
文章分类

全部博文(674)

文章存档

2013年(34)

2012年(146)

2011年(197)

2010年(297)

分类: LINUX

2012-07-11 11:17:44

GPIO驱动大家都很熟悉,这里就不再说了,那什么是TimedGPIO驱动呢?从名字可以看出来,该驱动和时间有很大关系。不错,TimedGPIO就是将普通的GPIO驱动和内核定时器进行了绑定,从而实现了一个时钟GPIO,即受时钟控制的GPIO。

  1. 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

涉及的文件数量比前面的一些驱动多了

但是其结构框架和实现过程却比其他驱动简单。从原理上已经可以大致看出其实现方式,但我们还是需要对其实现细节进行系统的分析。

  1. 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; inum_gpios;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; inum_gpios;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,则输出高电平,否则输出低电平。

阅读(1105) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~