ARM-Linux驱动开发 gpio-keys子系统源码 (1)-各个结构体内容解析及读设备树函数解析

gpio-keys.c

源码中的gpio-keys驱动(gpio-keys.c)按照input子系统和platform总线编写,驱动文件入口函数为platform_driver_register,即挂载platform驱动,出口函数为platform_driver_unregister,卸载platform驱动。

platform_driver结构体

static struct platform_driver key_driver = {
    .probe = key_probe,
    .remove = key_remove,
    .driver = {
        .name = "key",
        .of_match_table = of_key_match,
    },
};

定义 .probe入口函数和 .remove出口函数,定义 .driver,其中指明了查找设备树的结构体。

of_device_id结构体

static const struct of_device_id of_key_match[] = {
    {.compatible = "key",},
    {},
};

通过这个结构体从设备树中找到对应的设备,其中的.compatible属性应该与设备树中的一致。

probe 函数

platform 总线将驱动和设备分离,其中设备由设备树描述,驱动则通过 probe 函数挂载到 platform 总线上。当进入到这个函数时,说明已经通过 of_device_id 结构体的 .compatible 属性建立起 device 和 driver 之间的连接。
具体的,probe 函数做了以下工作:

  1. 遍历设备树的各个节点,读取相关数据
  2. 注册 platform driver
  3. 注册 input driver
  4. 创建 sysfs接口
  5. 注册 input device
    为实现上述步骤,建立了四个结构体管理相关数据,具体如下

gpio_keys_button结构体

这个结构体是描述gpio-key的相关属性的,在遍历设备树后直接将设备树的内容读到了这个结构体内,代码如下:

struct gpio_keys_button {
	unsigned int code;
	int gpio;
	int active_low;
	const char *desc;
	unsigned int type;
	int wakeup;
	int debounce_interval;
	bool can_disable;
	int value;
	unsigned int irq;
	struct gpio_desc *gpiod;
};

结构体中的各个元素的定义及含义:

  1. unsigned int code描述按键对应的事件代码,例如 KEY_*,SW_*等。对应的事件在 input.h中定义,置位相关的宏即可完成定义。
  2. int gpio按键对应的 GPIO 标签,一个标签对应一个引脚,可通过设备树更改,如果不支持 gpio 则值为 -1。在代码中,通过 of_get_gpio_flags 函数赋值 gpio 属性。
  3. int active_low电平激励方式,如果为 true 则表示低电平有效,反之高电平有效。
  4. const char *desc设备树中的 label 属性描述的对应信息,在代码中通过 button->desc = of_get_property(pp, “label”, NULL); 实现赋值。
  5. unsigned int type输入事件类型,例如 EV_KEY,EV_SW,EV-ABS等,在 input.h 中定义。
  6. int wakeup设备树中的 wakeup 属性,负责管理管脚休眠唤醒的功能。
  7. int debounce_interval负责按键防抖的延时,单位是ms。在代码中,通过下面的语句赋值,即使能防抖则设定防抖延时时间为5ms。
if (of_property_read_u32(pp, "debounce-interval", &button->debounce_interval)) button->debounce_interval = 5;
  1. bool can_disable是否允许用户空间通过 sysfs 使能或禁用这个 button。在代码中,通过下面的语句赋值,两次取反的目的是为了把函数的返回值变成 bool 类型。
button->can_disable = !!of_get_property(pp, "linux,can-disable", NULL);
  1. int value源码注释说是 EV_ABS 的轴值,实际使用作用不详,没有赋值。
  2. unsigned int irq描述这个子节点对应的中断,在源码中通过下面的语句赋值
button->irq = irq_of_parse_and_map(pp, 0);
  1. struct gpio_desc *gpiod源码注释说是 GPIO 描述符,实际使用作用不详,没有赋值。

gpio_keys_platform_data 结构体

这个结构体的作用是描述挂载到 platform 总线需要的一些参数,具体如下:

struct gpio_keys_platform_data {
	struct gpio_keys_button *buttons;
	int nbuttons;
	unsigned int poll_interval;
	unsigned int rep:1;
	int (*enable)(struct device *dev);
	void (*disable)(struct device *dev);
	const char *name;
};

结构体中各个元素的定义:

  1. struct gpio_keys_button *buttons指向 gpio_keys_button 结构体的指针。由于在设备树中,一个gpio-keys可能包含多个子节点,所以在管理所有的子节点使用结构体数组(指针)表示。
  2. int nbuttons指设备树中的一个设备节点下的设备子节点的个数,同时也是 gpio_keys_button 结构体的个数,还是实际上使用的按键的个数。
  3. unsigned int poll_interval poll机制的轮询时间间隔(ms),在源码中没有使用,作用未知。
  4. unsigned int rep:1使能输入子系统的自动使能,默认为1(使能)。赋值语句如下
key_data->rep = !!of_get_property(node, "autorepeat", NULL);
  1. int (*enable)(struct device *dev)一个函数指针,函数的参数是 device 指针,返回值是 int,指向启用设备的函数。
  2. int (*disable)(struct device *dev)一个函数指针,函数的参数是 device 指针,返回值是 int,指向禁用设备的函数。
  3. const char *nameinput设备名称。

gpio_button_data 结构体

这个结构体用来描述具体的用来 probe 的相关数据,其实就是把 gpio_keys_button 结构体进行一个转换(翻译),从而可以直接作为某些函数的参数,这个结构体仍然是描述某一个具体的子节点(某一个按键),而不是描述整个设备(全部按键)。

struct gpio_button_data {
    const struct gpio_keys_button *button;
    struct input_dev *input;
    struct timer_list release_timer;
    unsigned int release_delay;
    struct delayed_work work;
    unsigned int software_debounce;
    unsigned int irq;
    spinlock_t lock;
    bool disabled;
    bool key_pressed;
};

结构体中的各个元素定义如下:

  1. const struct gpio_keys_button *buttongpio_keys_button类型的结构体指针
  2. struct input_dev *inputinput 结构体指针
  3. struct timer_list release_timer定时器结构体,描述用于防抖的定时器。
  4. unsigned int release_delay用于防抖的定时时间,就是gpio_keys_button结构体的 debounce_interval。在源码中赋值如下
bdata->release_delay = button->debounce_interval;
  1. struct delayed_work work仍然是与按键防抖相关的,一个延时工作队列。
  2. unsigned int software_debounce仍然与按键防抖相关,也是按键防抖的定时时间,实际上在源码 中这个参数才是真正被使用的防抖定时器延时时间,其值与 release_delaydebounce_interval相同,赋值语句如下:
if (button->debounce_interval) {
    /* 按键防抖相关, 设置 debounce*/
    error = gpio_set_debounce(button->gpio, button->debounce_interval * 1000);
    if (error < 0) bdata->software_debounce = button->debounce_interval;
}
  1. unsigned int irq描述中断。
  2. spinlock_t lock自旋锁,防止多线程时资源竞争。
  3. bool disabled描述这个按键(一个设备子节点)是否使能。
  4. bool key_pressed描述这个按键(一个设备子节点)是否被按下。

gpio_keys_drvdata结构体

这个结构体用来描述 gpio-keys 驱动需要使用的数据(没有也可以,创立的原因我猜是为了作为函数的输入,其实就是上面结构体的组合,描述整个设备)

struct gpio_keys_drvdata {
    const struct gpio_keys_platform_data *pdata;
    struct input_dev *input;
    struct mutex disable_lock;
    struct gpio_button_data data[0];
};

结构体中的各个元素定义如下:

  1. const struct gpio_keys_platform_data *pdata显而易见,gpio_keys_platform_data结构体。
  2. struct input_dev *input显而易见,input 结构体指针,描述 input 子系统。
  3. struct mutex disable_lock互斥体
  4. struct gpio_button_data data[0]显而易见,gpio_button_data data数组,描述多个设备子节点。

gpio_keys_get_devtree_pdata函数,遍历设备树的各个节点,读取相关数据

函数内容如下:

static struct gpio_keys_platform_data *
gpio_keys_get_devtree_pdata(struct device *dev)
{
	struct device_node *node, *pp;
	struct gpio_keys_platform_data *pdata;
	struct gpio_keys_button *button;
	int error;
	int nbuttons;
	int i;
	node = dev->of_node;
	if (!node)
		return ERR_PTR(-ENODEV);
	nbuttons = of_get_child_count(node);
	if (nbuttons == 0)
		return ERR_PTR(-ENODEV);

	pdata = devm_kzalloc(dev,
			     sizeof(*pdata) + nbuttons * sizeof(*button),
			     GFP_KERNEL);
	if (!pdata)
		return ERR_PTR(-ENOMEM);
	pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
	pdata->nbuttons = nbuttons;
	pdata->rep = !!of_get_property(node, "autorepeat", NULL);
	i = 0;
	for_each_child_of_node(node, pp) {
		enum of_gpio_flags flags;
		button = &pdata->buttons[i++];
		button->gpio = of_get_gpio_flags(pp, 0, &flags);
		if (button->gpio < 0) {
			error = button->gpio;
			if (error != -ENOENT) {
				if (error != -EPROBE_DEFER)
					dev_err(dev,
						"Failed to get gpio flags, error: %d\n",
						error);
				return ERR_PTR(error);
			}
		} else {
			button->active_low = flags & OF_GPIO_ACTIVE_LOW;
		}
		button->irq = irq_of_parse_and_map(pp, 0);
		if (!gpio_is_valid(button->gpio) && !button->irq) {
			dev_err(dev, "Found button without gpios or irqs\n");
			return ERR_PTR(-EINVAL);
		}
		if (of_property_read_u32(pp, "linux,code", &button->code)) {
			dev_err(dev, "Button without keycode: 0x%x\n",
				button->gpio);
			return ERR_PTR(-EINVAL);
		}
		button->desc = of_get_property(pp, "label", NULL);
		if (of_property_read_u32(pp, "linux,input-type", &button->type))
			button->type = EV_KEY;
		button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);
		button->can_disable = !!of_get_property(pp, "linux,can-disable", NULL);
		if (of_property_read_u32(pp, "debounce-interval",
					 &button->debounce_interval))
			button->debounce_interval = 5;
	}

	if (pdata->nbuttons == 0)
		return ERR_PTR(-EINVAL);
	return pdata;
}

这个函数在 gpio_keys_probe函数最开始的时候调用,作用是遍历根据设备节点compatible属性找到的节点下的各个子节点,实际上就是遍历所有使用的按键。

struct device_node *node, *pp;

定义了两个设备节点 nodepp,其中node是作为设备节点,pp是该节点下的子节点。

struct gpio_keys_platform_data *pdata;

定义gpio_keys_platform_data 结构体,该结构体用来描述 platform 总线设备属性,实际上就是自定义的能够描述所有设备子节点的结构体。

struct gpio_keys_button *button;

描述设备子节点的结构体。

int error;

描述错误的变量,作为调用的某些函数的返回值,特别是包含堆或者指针操作的函数,例如kmalloc

int nbuttons;

设备子节点的个数、按键的个数或者 gpio_keys_button结构体的个数(三个实际上含义是一样的)。

node = dev->of_node;

赋值设备节点。dev是传入的参数,含义是device结构体指针,描述device设备。实际上只要能够进入probe函数,就说明已经找到了设备树中对应的device。device结构体中的of_node是一个void类型的指针,与设备树有关,实际上就是描述找到的设备节点。

if (!node)
		return ERR_PTR(-ENODEV);

如果没有找到node,则返回 error。

nbuttons = of_get_child_count(node);

查看这个设备节点下有多少个子节点,然后存储在nbuttons中。

if (nbuttons == 0)
		return ERR_PTR(-ENODEV);

如果没有子节点,说明没有任何按键,返回 error。

pdata = devm_kzalloc(dev,
			     sizeof(*pdata) + nbuttons * sizeof(*button),
			     GFP_KERNEL);

gpio_keys_platform_data结构体开辟一块空间,用来存储描述这个设备节点的数据。

if (!pdata)
		return ERR_PTR(-ENOMEM);

指针操作一定要判断并返回error。

pdata->buttons = (struct gpio_keys_button *)(pdata + 1);

pdata +1的地址空间强制类型转换为struct gpio_keys_button,然后另pdata->buttons指针指向这个空间。实际上就是对上面开辟的空间进行定义,pdata + 1指向的空间就是用来存放*button的。

pdata->nbuttons = nbuttons;

一个赋值,没什么好说的。

pdata->rep = !!of_get_property(node, "autorepeat", NULL);

这里进行了一个设备树的查询,查询当前设备节点的autorepeat属性,这个属性顾名思义,就是输入子系统的自动使能功能,显然这个属性是在设备节点中定义,而非设备子节点。

i = 0;

i是数组的下标,也是遍历设备子节点的个数(第几个),从0开始。

for_each_child_of_node(node, pp)

这个宏顾名思义,就是遍历所有的设备子节点,其中node是节点,pp是当前正在遍历的子节点。从这行代码开始,以下都是循环体内的内容。

enum of_gpio_flags flags;

定义一个of_gpio_flags 的枚举,顾名思义,这个枚举一定和设备树有关系。这个枚举的作用应该是找到具体的GPIO的一个地址映射,或者是对GPIO激励方式的一个定义(我猜测),这里应该可以自己改写。这个枚举内仅存了一个OF_GPIO_ACTIVE_LOW

button = &pdata->buttons[i++];

一个赋值,后续使用button就代表pdata->buttons数组的第i - 1个元素。

button->gpio = of_get_gpio_flags(pp, 0, &flags);

读设备树gpio信息。

if (button->gpio < 0) {
			error = button->gpio;
			if (error != -ENOENT) {
				if (error != -EPROBE_DEFER)
					dev_err(dev,
						"Failed to get gpio flags, error: %d\n",
						error);
				return ERR_PTR(error);
			}
		} else {
			button->active_low = flags & OF_GPIO_ACTIVE_LOW;
		}

如果button->gpio < 0说明读取错误,读到了就赋值对应的激励方式。

button->irq = irq_of_parse_and_map(pp, 0);

读设备树中的中断描述。

if (!gpio_is_valid(button->gpio) && !button->irq) {
			dev_err(dev, "Found button without gpios or irqs\n");
			return ERR_PTR(-EINVAL);
		}

gpio_is_valid函数返回falsebutton->irq为0,返回error。gpio_is_valid函数顾名思义,应该是一个判断gpio是否合法的函数,但在源码中直接返回了false。这句话意思应该是gpio不合法且中断也没有找到或者没有开启,就返回error。

if (of_property_read_u32(pp, "linux,code", &button->code)) {
			dev_err(dev, "Button without keycode: 0x%x\n",
				button->gpio);
			return ERR_PTR(-EINVAL);
		}

读设备树中的按键事件,存储到button->code

button->desc = of_get_property(pp, "label", NULL);

lable标签的内容存储到button->desc

if (of_property_read_u32(pp, "linux,input-type", &button->type))
			button->type = EV_KEY;

type,没读到就给EV_KEYtype

button->can_disable = !!of_get_property(pp, "linux,can-disable", NULL);

读使能。

if (of_property_read_u32(pp, "debounce-interval",
					 &button->debounce_interval))
			button->debounce_interval = 5;

读防抖,如果没读到就设定5ms的防抖延时时间。

if (pdata->nbuttons == 0)
		return ERR_PTR(-EINVAL);

这里退出循环了,再次判断一下子节点的数目。

return pdata;

返回,函数结束。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值