Freescale i.MX53 GPIO 按键驱动
硬件平台:IMX53-QSB
内核版本:LINUX-2.6.35.3
系统版本:ANDROID 2.3.4
一、GPIO 的使用
按键的处理需要读取相应 IO 引脚的值,阅读 IMX53 处理器芯片手册,得知将 GPIO 读模式大的步骤如下:
1. 通过设置 IOMUX 将相应引脚配置为 GPIO 模式,控制的寄存器是 IOMUXC_SW_MUX_CTL_PAD_XXX
2. 配置 GPIO 的方向为输入,控制的寄存器是 GPIOx_GDIR ,0表示输入,1表示输出
3. 读取相应 GPIO 引脚的值,读取的寄存器为 GPIOx_PSR
第1步,配置为 gpio 模式(以 MX53_PAD_GPIO_2__GPIO1_2 为例)
在文件 arch/arm/mach-mx5/mx53_loco.c 中
板子初始化函数 mxc_board_init 中,调用了 io 初始化函数 mx53_loco_io_init :
static void __init mxc_board_init(void)
{
mx53_loco_io_init();
}
mx53_loco_io_init 函数中调用 mxc_iomux_v3_setup_multiple_pads 函数配置引脚:
static void __init mx53_loco_io_init(void)
{
mxc_iomux_v3_setup_multiple_pads(mx53_loco_pads,ARRAY_SIZE(mx53_loco_pads));
}
mx53_loco_pads 为一 u64 类型的数组:
static iomux_v3_cfg_t mx53_loco_pads[] = {
MX53_PAD_GPIO_2__GPIO1_2,
};
MX53_PAD_GPIO_2__GPIO1_2 定义在 arch/arm/plat-mxc/include/mach/iomux-mx53.h
#define MX53_PAD_GPIO_2__GPIO1_2 (_MX53_PAD_GPIO_2__GPIO1_2 | MUX_PAD_CTRL(NO_PAD_CTRL))
MX53_PAD_GPIO_2__GPIO1_2 由 _MX53_PAD_GPIO_2__GPIO1_2 与 MUX_PAD_CTRL(NO_PAD_CTRL) 按位或得到,
先来看 _MX53_PAD_GPIO_2__GPIO1_2 :
#define _MX53_PAD_GPIO_2__GPIO1_2 IOMUX_PAD(0x6B8, 0x328, 1, 0x0, 0, 0)
IOMUX_PAD 的定义在 arch/arm/plat-mxc/include/mach/iomux-v3.h
#define IOMUX_PAD(_pad_ctrl_ofs, _mux_ctrl_ofs, _mux_mode, _sel_input_ofs,_sel_input, _pad_ctrl) \
( ((iomux_v3_cfg_t)(_mux_ctrl_ofs) << MUX_CTRL_OFS_SHIFT)
|
\
((iomux_v3_cfg_t)(_mux_mode) << MUX_MODE_SHIFT)
|
\
((iomux_v3_cfg_t)(_pad_ctrl_ofs) << MUX_PAD_CTRL_OFS_SHIFT)
|
\
((iomux_v3_cfg_t)(_pad_ctrl) << MUX_PAD_CTRL_SHIFT)
|
\
((iomux_v3_cfg_t)(_sel_input_ofs) << MUX_SEL_INPUT_OFS_SHIFT)
|
\
((iomux_v3_cfg_t)(_sel_input) << MUX_SEL_INPUT_SHIFT) )
#define MUX_CTRL_OFS_SHIFT
0
#define MUX_PAD_CTRL_OFS_SHIFT
12
#define MUX_SEL_INPUT_OFS_SHIFT
24
#define MUX_MODE_SHIFT
36
#define MUX_PAD_CTRL_SHIFT
41
#define MUX_SEL_INPUT_SHIFT
58
那么 IOMUX_PAD 是这个样子:
__________
|61|60|59|58|
_sel_input
__________________________________________
|57|56|55|54|53|52|51|50|49|48|47|46|45|44|43|42|41|
_pad_ctrl
_____________
|40|39|38|37|36|
_mux_mode
______________________________
|35|34|33|32|31|30|29|28|27|26|25|24|
_sel_input_ofs
______________________________
|23|22|21|20|19|18|17|16|15|14|13|12|
_pad_ctrl_ofs
________________________
|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0|
_mux_ctrl_ofs
这样 _MX53_PAD_GPIO_2__GPIO1_2 即 IOMUX_PAD(0x6B8, 0x328, 1, 0x0, 0, 0) 是这个样子:
__________
|61|60|59|58|
0
__________________________________________
|57|56|55|54|53|52|51|50|49|48|47|46|45|44|43|42|41|
0
____________
|40|39|38|37|36|
1
______________________________
|35|34|33|32|31|30|29|28|27|26|25|24|
0x0
______________________________
|23|22|21|20|19|18|17|16|15|14|13|12|
0x6B8
________________________
|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0|
0x328
再来看看 MUX_PAD_CTRL(NO_PAD_CTRL)
#define MUX_PAD_CTRL(x) ((iomux_v3_cfg_t)(x) << MUX_PAD_CTRL_SHIFT)
#define NO_PAD_CTRL ((iomux_v3_cfg_t)1 << (MUX_PAD_CTRL_SHIFT + 16))
这 MUX_PAD_CTRL(NO_PAD_CTRL) 将 1 左移了 98 位,代表什么意思?
这样 MX53_PAD_GPIO_2__GPIO1_2 的值就确定了。
mxc_iomux_v3_setup_multiple_pads 定义在 arch/arm/plat-mxc/iomux-v3.c :
int mxc_iomux_v3_setup_multiple_pads(iomux_v3_cfg_t *pad_list, unsigned count)
{
iomux_v3_cfg_t *p = pad_list;
int i;
/* 通过一个循环将 mx53_loco_pads 中的每个成员都使用 mxc_iomux_v3_get_pad 进行配置 */
for (i = 0; i < count; i++) {
mxc_iomux_v3_get_pad(p);
p++;
}
return 0;
}
mxc_iomux_v3_setup_pad 函数定义:
int mxc_iomux_v3_setup_pad(iomux_v3_cfg_t pad)
{
/* 这里的 pad 即是 mx53_loco_pads 数组中的 MX53_PAD_GPIO_2__GPIO1_2
* 以下 6 行即获得 MX53_PAD_GPIO_2__GPIO1_2 中的
* _pad_ctrl_ofs, _mux_ctrl_ofs, _mux_mode, _sel_input_ofs,_sel_input, _pad_ctrl
*/
u32 mux_ctrl_ofs = (pad & MUX_CTRL_OFS_MASK) >> MUX_CTRL_OFS_SHIFT; // 0x328
u32 mux_mode = (pad & MUX_MODE_MASK) >> MUX_MODE_SHIFT;
u32 sel_input_ofs = (pad & MUX_SEL_INPUT_OFS_MASK) >> MUX_SEL_INPUT_OFS_SHIFT;
u32 sel_input = (pad & MUX_SEL_INPUT_MASK) >> MUX_SEL_INPUT_SHIFT;
u32 pad_ctrl_ofs = (pad & MUX_PAD_CTRL_OFS_MASK) >> MUX_PAD_CTRL_OFS_SHIFT;
u32 pad_ctrl = (pad & MUX_PAD_CTRL_MASK) >> MUX_PAD_CTRL_SHIFT;
/* 将相关配置值写入相应的寄存器 */
if (mux_ctrl_ofs)
/* 地址偏移 0x328 的寄存器是 IOMUXC_SW_MUX_CTL_PAD_GPIO_2 ,mux_mode = 1 即为 gpio 模式 */
__raw_writel(mux_mode, base + mux_ctrl_ofs);
if (sel_input_ofs)
/* 地址偏移 0x0 的寄存器是 IOMUXC_GPR0 */
__raw_writel(sel_input, base + sel_input_ofs);
if (!(pad_ctrl & NO_PAD_CTRL) && pad_ctrl_ofs)
/* 地址偏移 0x6B8 的寄存器是 IOMUXC_SW_PAD_CTL_PAD_GPIO_2 */
__raw_writel(pad_ctrl, base + pad_ctrl_ofs);
return 0;
}
如果需要将某 IO 引脚 XXX_GPIO_XXX 设置为 GPIO 模式,将 XXX_GPIO_XXX 添加到 mx53_loco_pads 数组即可。
第2步,将 gpio 设置为输入
使用的函数为 int gpio_direction_input(unsigned gpio)
定义在 drivers/gpio/gpiolib.c
int gpio_direction_input(unsigned gpio)
{
unsigned long
flags;
struct gpio_chip
*chip;
struct gpio_desc
*desc = &gpio_desc[gpio];
int
status = -EINVAL;
spin_lock_irqsave(&gpio_lock, flags);
if (!gpio_is_valid(gpio))
goto fail;
chip = desc->chip;
if (!chip || !chip->get || !chip->direction_input)
goto fail;
gpio -= chip->base;
if (gpio >= chip->ngpio)
goto fail;
status = gpio_ensure_requested(desc, gpio);
if (status < 0)
goto fail;
/* now we know the gpio is valid and chip won't vanish */
spin_unlock_irqrestore(&gpio_lock, flags);
might_sleep_if(extra_checks && chip->can_sleep);
if (status) {
status = chip->request(chip, gpio);
if (status < 0) {
pr_debug("GPIO-%d: chip request fail, %d\n",
chip->base + gpio, status);
/* and it's not available to anyone else ...
* gpio_request() is the fully clean solution.
*/
goto lose;
}
}
status = chip->direction_input(chip, gpio);
if (status == 0)
clear_bit(FLAG_IS_OUT, &desc->flags);
lose:
return status;
fail:
spin_unlock_irqrestore(&gpio_lock, flags);
if (status)
pr_debug("%s: gpio-%d status %d\n",
__func__, gpio, status);
return status;
}
该函数调用了 chip->direction_input(chip, gpio);
最终调用到的是 arch/arm/plat-mxc/gpio.c 文件中的函数 mxc_gpio_direction_input ,原型如下:
static int mxc_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
/* 调用 _set_gpio_direction ,传递的第 3 个参数为 0 ,方向设置为输入 */
_set_gpio_direction(chip, offset, 0);
return 0;
}
_set_gpio_direction 函数原型:
static void _set_gpio_direction(struct gpio_chip *chip, unsigned offset,int dir)
{
struct mxc_gpio_port *port =
container_of(chip, struct mxc_gpio_port, chip);
u32 l;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
l = __raw_readl(port->base + GPIO_GDIR);
if (dir)
l |= 1 << offset;
else
l &= ~(1 << offset);
/* 传入的参数 dir=0,故向相应 GPIO 的 GDIR 寄存器写入 0 */
__raw_writel(l, port->base + GPIO_GDIR);
spin_unlock_irqrestore(&port->lock, flags);
}
第3步,获取 GPIO 引脚的值
使用的宏为 gpio_get_value
arch/arm/plat-mxc/include/mach/gpio.h
#define gpio_get_value __gpio_get_value
__gpio_get_value 函数原型定义在 drivers/gpio/gpiolib.c
int __gpio_get_value(unsigned gpio)
{
struct gpio_chip
*chip;
chip = gpio_to_chip(gpio);
WARN_ON(extra_checks && chip->can_sleep);
return chip->get ? chip->get(chip, gpio - chip->base) : 0;
}
__gpio_get_value 调用 chip->get ,实际调用到的是 arch/arm/plat-mxc/gpio.c 文件中 mxc_gpio_get 函数
static int mxc_gpio_get(struct gpio_chip *chip, unsigned offset)
{
struct mxc_gpio_port *port =
container_of(chip, struct mxc_gpio_port, chip);
/* 读取相关寄存器的 PSR 寄存器 */
return (__raw_readl(port->base + GPIO_PSR) >> offset) & 1;
}
二、PLATFORM DEVICE
GPIO按键作为系统的一种设备,挂载到platform总线上,相应的platform device定义在arch/arm/mach-mx5/mx53_loco.c:
static struct platform_device loco_button_device = {
.name = "gpio-keys",
.id = -1,
.num_resources = 0,
.dev = {
.platform_data = &loco_button_data,
}
};
loco_button_data 信息如下:
static struct gpio_keys_platform_data loco_button_data = {
.buttons = loco_buttons,
.nbuttons = ARRAY_SIZE(loco_buttons),
};
loco_buttons 定义了5个按键:power、back、home、volumeup、volumedown和menu。
static struct gpio_keys_button loco_buttons[] = {
GPIO_BUTTON(MX53_nONKEY, KEY_POWER, 1, "power", 0),
GPIO_BUTTON(USER_UI1, KEY_BACK, 1, "back", 0),
GPIO_BUTTON(USER_UI2, KEY_HOME, 1, "home", 0),
GPIO_BUTTON(KEY_VOLUP, KEY_VOLUMEUP, 1, "volumeup", 0),
GPIO_BUTTON(KEY_VOLDOWN, KEY_VOLUMEDOWN, 1, "volumedown", 0),
GPIO_BUTTON(KEY_SET, KEY_MENU, 1, "menu", 0),
};
宏 GPIO_BUTTON 的内容为:
#define GPIO_BUTTON(gpio_num, ev_code, act_low, descr, wake) \
{ \
.gpio = gpio_num, \
.type = EV_KEY, \
.code = ev_code, \
.active_low = act_low, \
.desc = "btn " descr, \
.wakeup = wake, \
}
第一个参数为 gpio 引脚,第二个参数为按键的键值。
loco_buttons 使用的 gpio 定义如下:
#define MX53_nONKEY (0*32 + 8) /* GPIO_1_8 */
#define USER_UI1 (1*32 + 14) /* GPIO_2_14 */
#define USER_UI2 (1*32 + 15) /* GPIO_2_15 */
#define KEY_VOLUP (6*32 + 13) /* GPIO_7_13 */
#define KEY_VOLDOWN (0*32 + 4) /* GPIO_1_4 */
#define KEY_SET (0*32 + 2) /* GPIO_1_2 */
键值定义在 include/linux/input.h 文件中:
#define KEY_HOME 102
#define KEY_VOLUMEDOWN 114
#define KEY_VOLUMEUP 115
#define KEY_POWER 116 /* SC System Power Down */
#define KEY_MENU 139 /* Menu (show menu) */
#define KEY_BACK 158 /* AC Back */
在板子初始化函数 static void __init mxc_board_init(void) 中
调用了 loco_add_device_buttons() 函数将loco_button_device加入到系统中,
这样驱动即可匹配 .name 找到我们的设备loco_button_device。
loco_add_device_buttons() 函数定义如下:
static void __init loco_add_device_buttons(void)
{
platform_device_register(&loco_button_device);
}
三、PLATFORM DRIVER
GPIO按键的驱动文件为:drivers/input/keyboard/gpio_keys.c
通过 module_init ,在总线上注册 name 为 gpio-keys 的驱动,并通过 module_exit 相应的将其注销:
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}
static void __exit gpio_keys_exit(void)
{
platform_driver_unregister(&gpio_keys_device_driver);
}
module_init(gpio_keys_init);
module_exit(gpio_keys_exit);
platform driver - gpio_keys_device_driver 如下:
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = __devexit_p(gpio_keys_remove),
.driver = {
.name = "gpio-keys",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &gpio_keys_pm_ops,
#endif
}
};
在探测到 name 匹配的 device 之后,gpio_keys_probe 得以执行:
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 input_dev *input;
int i, error;
int wakeup = 0;
/* 为ddata 分配空间 */
ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data),
GFP_KERNEL);
/* 分配一个输入设备 */
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;
mutex_init(&ddata->disable_lock);
/* 将 pdev 与 ddata 联系在一起,ddata 成为了 pdev 的平台data */
platform_set_drvdata(pdev, ddata);
input->name = pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit);
/* 设置 loco_buttons 中各个 gpio 的中断及按键功能 */
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);
}
/* 将我们的设备添加到 sys 文件系统 */
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;
}
/* 将我们的设备注册到输入子系统 */
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 (pdata->buttons[i].debounce_interval)
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);
return error;
}
gpio_keys_setup_key 函数内容 :
static int __devinit gpio_keys_setup_key(struct platform_device *pdev,
struct gpio_button_data *bdata,
struct gpio_keys_button *button)
{
char *desc = button->desc ? button->desc : "gpio_keys";
struct device *dev = &pdev->dev;
unsigned long irqflags;
int irq, error;
setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata);
/* 初始化工作队列,当调度 &bdata->work 时,函数 gpio_keys_work_func 得以执行 */
INIT_WORK(&bdata->work, gpio_keys_work_func);
/* 请求 gpio */
error = gpio_request(button->gpio, desc);
if (error < 0) {
dev_err(dev, "failed to request GPIO %d, error %d\n",
button->gpio, error);
goto fail2;
}
/* 设置 loco_buttons 中涉及的 gpio 为输入模式 */
error = gpio_direction_input(button->gpio);
if (error < 0) {
dev_err(dev, "failed to configure"
" direction for GPIO %d, error %d\n",
button->gpio, error);
goto fail3;
}
/* 获取 gpio 中断号 */
irq = gpio_to_irq(button->gpio);
if (irq < 0) {
error = irq;
dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
goto fail3;
}
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
/*
* If platform has specified that the button can be disabled,
* we don't want it to share the interrupt line.
*/
if (!button->can_disable)
irqflags |= IRQF_SHARED;
/* 请求中断,多个按键共用一个处理函数 */
error = request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);
if (error) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
irq, error);
goto fail3;
}
return 0;
fail3:
gpio_free(button->gpio);
fail2:
return error;
}
中断处理函数 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));
/* 这里 button->debounce_interval = 0 */
if (button->debounce_interval)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(button->debounce_interval));
else
/* 调度 &bdata->work,gpio_keys_work_func 的以执行 */
schedule_work(&bdata->work);
return IRQ_HANDLED;
}
gpio_keys_work_func 的定义:
static void gpio_keys_work_func(struct work_struct *work)
{
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work);
/* 获取按键状态并且上报 */
gpio_keys_report_event(bdata);
}
函数 gpio_keys_report_event 内容:
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(button->gpio) ? 1 : 0) ^ button->active_low;
/* 将相应功能按键的相应状态上报给 input 子系统 */
input_event(input, type, button->code, !!state);
/* 同步事件,通知事件的接收方,本次事件已完成 */
input_sync(input);
}
如果要添加或是修改 android 的按键功能,需保证 device/fsl/imx53_loco/gpio-keys.kl 文件中有相应的键值,可以参考 qwerty.kl 文件修改。
以上内容为个人对 IMX53 GPIO 按键驱动的认识,如有不妥之处,还望批评指正。