i.MX53 GPIO 按键驱动

时间:2022-08-18 09:26:48

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 按键驱动的认识,如有不妥之处,还望批评指正。