做个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 】命令来查看驱动加载结果,如下图: