嵌入式Linux的GPIO中断

做个STM32开发的都知道,通过程序配置让普通的GPIO口具有中断功能。这样对于设备检测某些硬件的自动设备要方便的多。那么在嵌入式的Linux是否具有这样的功能呢?答案是肯定的,下面我们就来介绍一下如何配置嵌入式Linux的GPIO中断。

1.内核中配置GPIO中断
Linux内核的输入子系统是对分散的、多种不同类别的输入设备(如键盘、鼠标、跟踪球、操作杆、触摸屏、加速计和手写板)进行统一处理的驱动程序。输入子系统带来的好处:
a.统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论是PS/2、USB,还是蓝牙,都做同样的处理;
b.提供了用于分发输入报告给用户应用程序的简单的事件接口;
c.抽取出了输入驱动程序的通用部分,简化了驱动程序,并引入了一致性;
在内核中,按键的驱动已经完成!!!不需要我们自己写。driver/input/keyboard/gpio_keys.c 就是驱动文件。关于源码大家请下载Linux内核源码进行查看,下面介绍几个重点函数的实现。

1.1 gpio_keys_probe 函数
gpio_keys_probe函数在gpio_keys.c文件中,源码如下:

static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
	struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
	struct gpio_keys_drvdata *ddata;
	struct device *dev = &pdev->dev;
	struct gpio_keys_platform_data alt_pdata;
	struct input_dev *input;
	int i, error;
	int wakeup = 0;

	if (!pdata) {
		error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);
		if (error)
			return error;
		pdata = &alt_pdata;
	}
	
	 /*kzalloc 对kmalloc的封装,会清0分配的空间*/  
	ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
			pdata->nbuttons * sizeof(struct gpio_button_data),
			GFP_KERNEL);

	/*分配一个input设备*/  
	input = input_allocate_device();
	if (!ddata || !input) {
		dev_err(dev, "failed to allocate state\n");
		error = -ENOMEM;
		goto fail1;
	}

	ddata->input = input;
	ddata->n_buttons = pdata->nbuttons;
	ddata->enable = pdata->enable;
	ddata->disable = pdata->disable;
	mutex_init(&ddata->disable_lock);

	/* 设置input设备属性 */
	platform_set_drvdata(pdev, ddata);
	input_set_drvdata(input, ddata);

	input->name = pdata->name ? : pdev->name;
	input->phys = "gpio-keys/input0";
	input->dev.parent = &pdev->dev;
	input->open = gpio_keys_open;
	input->close = gpio_keys_close;

	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;

	/* 使能系统的自动清除中断标志位功能 */
	if (pdata->rep)
		__set_bit(EV_REP, input->evbit);

	for (i = 0; i < pdata->nbuttons; i++) {
		struct gpio_keys_button *button = &pdata->buttons[i];
		struct gpio_button_data *bdata = &ddata->data[i];
		unsigned int type = button->type ?: EV_KEY;

		bdata->input = input;
		bdata->button = button;

		error = gpio_keys_setup_key(pdev, bdata, button);
		if (error)
			goto fail2;

		if (button->wakeup)
			wakeup = 1;

		input_set_capability(input, type, button->code);
	}

	error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
	if (error) {
		dev_err(dev, "Unable to export keys/switches, error: %d\n",
			error);
		goto fail2;
	}
	
	 /*注册input设备*/  
	error = input_register_device(input);
	if (error) {
		dev_err(dev, "Unable to register input device, error: %d\n",
			error);
		goto fail3;
	}

	/* get current state of buttons */
	for (i = 0; i < pdata->nbuttons; i++)
		gpio_keys_report_event(&ddata->data[i]);
	input_sync(input);

	device_init_wakeup(&pdev->dev, wakeup);

	return 0;

 fail3:
	sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
 fail2:
	while (--i >= 0) {
		free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
		if (ddata->data[i].timer_debounce)
			del_timer_sync(&ddata->data[i].timer);
		cancel_work_sync(&ddata->data[i].work);
		gpio_free(pdata->buttons[i].gpio);
	}

	platform_set_drvdata(pdev, NULL);
 fail1:
	input_free_device(input);
	kfree(ddata);
	/* If we have no platform_data, we allocated buttons dynamically. */
	if (!pdev->dev.platform_data)
		kfree(pdata->buttons);

	return error;
}

1.2 irqreturn_t gpio_keys_isr 函数
中断服务源码如下:

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
	struct gpio_button_data *bdata = dev_id;
	struct gpio_keys_button *button = bdata->button;

	BUG_ON(irq != gpio_to_irq(button->gpio));
	/*检测是否在platform device设置了去抖动*/  
	if (bdata->timer_debounce)
		mod_timer(&bdata->timer, /* 延迟msecs_to_jiffies(button->debounce_interval)个jiffies后执行schedule_work()*/  
			jiffies + msecs_to_jiffies(bdata->timer_debounce));
	else
		schedule_work(&bdata->work);

	return IRQ_HANDLED;
}

这里的中断被分成了上半部和下半部,上半部(中断处理例程)对中断进行了快速的相应,马上调度work上报事件信息:

static void gpio_keys_report_event(struct gpio_button_data *bdata)
{
	struct gpio_keys_button *button = bdata->button;
	struct input_dev *input = bdata->input;
	unsigned int type = button->type ?: EV_KEY;
	int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;

	if (type == EV_ABS) {
		if (state)
			input_event(input, type, button->code, button->value);
	} else {
		input_event(input, type, button->code, !!state);
	}
	input_sync(input);
}

1.3 内核加载驱动
Linux内核在启动的过程中加载相关驱动的源码路径:arch/arm/mach-omap2/board-am335xevm.c
首先设置GPIO管脚的I/O模式,设置如下:

static struct pinmux_config gpio_keys_pin_mux_forlinx[] = {
#if defined(CONFIG_OK335XD)
    {"gpmc_a4.gpio1_20", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_a5.gpio1_21", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_a6.gpio1_22", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
	{"gpmc_a7.gpio1_23", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_a8.gpio1_24", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_a9.gpio1_25", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
#elif defined(CONFIG_OK335XS)
    {"xdma_event_intr0.gpio0_19", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_csn3.gpio2_0", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"mcasp0_fsx.gpio3_15", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_csn1.gpio1_30", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"mcasp0_aclkx.gpio3_14", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
#elif defined(CONFIG_OK335XS2)//add by zrj
    {"gpmc_a4.gpio1_20", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_a5.gpio1_21", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
    {"gpmc_a6.gpio1_22", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT},
#endif
    {NULL, 0},
};
/* Configure GPIOs for GPIO Keys */
static struct gpio_keys_button am335x_evm_gpio_buttons_forlinx[] = {
#if defined(CONFIG_OK335XD)
	{
		.code                   = BTN_0,
		.gpio                   = GPIO_TO_PIN(1, 20),
		.desc                   = "SW1",
	},
	{
		.code                   = BTN_1,
		.gpio                   = GPIO_TO_PIN(1, 21),
		.desc                   = "SW2",
	},
	{
		.code                   = BTN_2,
		.gpio                   = GPIO_TO_PIN(1, 22),
		.desc                   = "SW3",
		.wakeup                 = 1,
	},
	{
		.code                   = BTN_3,
		.gpio                   = GPIO_TO_PIN(1, 23),
		.desc                   = "SW4",
	},
	{
		.code                   = BTN_4,
		.gpio                   = GPIO_TO_PIN(1, 24),
		.desc                   = "SW5",
		.wakeup                 = 1,
	},
	{
		.code                   = BTN_5,
		.gpio                   = GPIO_TO_PIN(1, 25),
		.desc                   = "SW6",
	},
#elif defined(CONFIG_OK335XS)
	{
                .code                   = BTN_0,
                .gpio                   = GPIO_TO_PIN(0, 19),
                .desc                   = "SW1",
        },
        {
                .code                   = BTN_1,
                .gpio                   = GPIO_TO_PIN(2, 0),
                .desc                   = "SW2",
        },
        {
                .code                   = BTN_2,
                .gpio                   = GPIO_TO_PIN(3, 15),
                .desc                   = "SW3",
                .wakeup                 = 1,
        },
        {
                .code                   = BTN_3,
                .gpio                   = GPIO_TO_PIN(1, 30),
                .desc                   = "SW4",
        },
        {
                .code                   = BTN_4,
                .gpio                   = GPIO_TO_PIN(3, 14),
                .desc                   = "SW5",
                .wakeup                 = 1,
        },

#elif defined(CONFIG_OK335XS2)
	{
		.code                   = BTN_0,
		.gpio                   = GPIO_TO_PIN(1, 20),
		.desc                   = "SW1",
	},
	{
		.code                   = BTN_1,
		.gpio                   = GPIO_TO_PIN(1, 21),
		.desc                   = "SW2",
	},
	{
		.code                   = BTN_2,
		.gpio                   = GPIO_TO_PIN(1, 22),
		.desc                   = "SW3",
		.wakeup                 = 1,
	},
#endif

};

初始化key,源码如下:

static struct gpio_keys_platform_data am335x_evm_gpio_key_info_forlinx = {
	.buttons        = am335x_evm_gpio_buttons_forlinx,
	.nbuttons       = ARRAY_SIZE(am335x_evm_gpio_buttons_forlinx),
};

static struct platform_device am335x_evm_gpio_keys_forlinx = {
	.name   = "gpio-keys",
	.id     = -1,
	.dev    = {
		.platform_data  = &am335x_evm_gpio_key_info_forlinx,
	},
};

static void keys_init(int evm_id, int profile)
{
	int err;
	setup_pin_mux(gpio_keys_pin_mux_forlinx);
	err = platform_device_register(&am335x_evm_gpio_keys_forlinx);
    if (err)
          pr_err("failed to register gpio key device\n");
}

key初始化加载到系统初始化函数,源码如下:

static struct evm_dev_cfg ok335x_dev_cfg[] = {
//    {gpio96_init, DEV_ON_BASEBOARD, PROFILE_ALL},
	{mmc1_rtl8189eus_init,DEV_ON_BASEBOARD,PROFILE_ALL},
	{mmc_init,	DEV_ON_BASEBOARD, PROFILE_ALL},
#if defined(CONFIG_ANDROID)
	{tscadc_init,DEV_ON_BASEBOARD, PROFILE_ALL},
#endif
	{net_init , DEV_ON_BASEBOARD, PROFILE_ALL},
//	{lcd_init , DEV_ON_BASEBOARD, PROFILE_ALL},
	{i2c_init , DEV_ON_BASEBOARD, PROFILE_ALL},
	{ecap_init, DEV_ON_BASEBOARD, PROFILE_ALL},
	{gpio31_init, DEV_ON_BASEBOARD, PROFILE_ALL},
//	{keys_init, DEV_ON_BASEBOARD, PROFILE_ALL},
//	{led_init , DEV_ON_BASEBOARD, PROFILE_ALL},
	{usb_init , DEV_ON_BASEBOARD, PROFILE_ALL},
	{nand_init, DEV_ON_BASEBOARD, PROFILE_ALL},
	{uart_init, DEV_ON_BASEBOARD, PROFILE_ALL},
	{spi_init , DEV_ON_BASEBOARD, PROFILE_ALL},
	{can_init , DEV_ON_BASEBOARD, PROFILE_ALL},
#ifndef CONFIG_OK335XS2
	{sound_init,DEV_ON_BASEBOARD, PROFILE_ALL},
#endif

//	{buzzer_init,DEV_ON_BASEBOARD, PROFILE_ALL},
#ifdef CONFIG_SP706P_WDT
	{sp706p_init,DEV_ON_BASEBOARD, PROFILE_ALL},
#endif
	{sgx_init , DEV_ON_BASEBOARD, PROFILE_ALL},
#ifndef CONFIG_FPGA
	{fpga_init , DEV_ON_BASEBOARD, PROFILE_ALL},
#endif
	{NULL, 0, 0},
};

通过上述的步骤,一个key驱动就加载进内核,我们可以通过【cat /proc/interrupts 】命令来查看驱动加载结果,如下图:

你可能感兴趣的:(Linux)