您的位置:首页 > 其它

学习笔记:音频之手机物理按键

2017-10-13 14:15 423 查看
(注:本文仅仅用于个人理顺代码流程思路)

手机物理按键驱动文件分析:

一、GPIO的DTS文件,代码段如下所示:

/*gpio口的DTS定义*/
gpio_keys {
status = "okay";
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&key_home &key_voldown &key_volup &key_power>;
button@1 {
label = "gpio-keys: KEY_HOMEPAGE";
interrupts = <4 0 0>;
interrupt-parent = <&gpa1>;
linux,code = <172>;
gpios = <&gpa1 4 0xf>;
gpio-key,wakeup = <1>;
};
button@2 {
label = "gpio-keys: KEY_VOLUMEDOWN";
interrupts = <6 0 0>;
interrupt-parent = <&gpa1>;
linux,code = <114>;
gpios = <&gpa1 6 0xf>;
};
button@3 {
label = "gpio-keys: KEY_VOLUMEUP";
interrupts = <5 0 0>;
interrupt-parent = <&gpa1>;
linux,code = <115>;
gpios = <&gpa1 5 0xf>;
};
button@4 {
label = "gpio-keys: KEY_POWER";
interrupts = <0 0 0>;
interrupt-parent = <&gpa0>;
linux,code = <116>;
gpios = <&gpa0 0 0xf>;
gpio-key,wakeup = <1>;
};
};


二、驱动文件gpio_keys.c:

几个重要的结构体:

/*该结构体用来定义GPIO的按键功能*/
struct gpio_keys_platform_data {
struct gpio_keys_button *buttons;       //指向gpio_keys_button结构体数组,用于按键的参数设置
int nbuttons;                           //按键数组内元素个数
unsigned int poll_interval;             //轮询时间间隔
unsigned int rep:1;                     //自动重复使能输入子系统
int (*enable)(struct device *dev);
void (*disable)(struct device *dev);
const char *name;                       //输入设备名
};

/*用来关联input系统*/
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];
};

/**
* struct gpio_keys_button - configuration parameters
* @code:       input event code (KEY_*, SW_*)
* @gpio:       %-1 if this key does not support gpio
* @active_low:     %true indicates that button is considered
*          depressed when gpio is low
* @desc:       label that will be attached to button's gpio
* @type:       input event type (%EV_KEY, %EV_SW, %EV_ABS)
* @wakeup:     configure the button as a wake-up source
* @debounce_interval:  debounce ticks interval in msecs
* @can_disable:    %true indicates that userspace is allowed to
*          disable button via sysfs
* @value:      axis value for %EV_ABS
* @irq:        Irq number in case of interrupt keys
* @gpiod:      GPIO descriptor
*/
/*按键参数设置*/
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、平台驱动注册:

static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}


平台驱动结构体gpio_keys_device_driver定义:

static struct platform_driver gpio_keys_device_driver = {
.probe      = gpio_keys_probe,
.remove     = gpio_keys_remove,
.driver     = {
.name   = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = of_match_ptr(gpio_keys_of_match),
}
};

static const struct of_device_id gpio_keys_of_match[] = {
{ .compatible = "gpio-keys", },
{ },
};


可以看到,平台驱动的name 是“gpio-keys”,linux统一设备模型的平台驱动与设备就是通过name进行匹配的,当驱动被注册进内核,该驱动就会去检索相同name的设备,找到后就会开始执行probe回调函数。很显然,该驱动文件对应的就是前面dts文件所定义的GPIO按键。

2、probe回调函数:

/*probe回调函数*/
gpio_keys_probe(struct platform_device *pdev)
|-- gpio_keys_get_devtree_pdata(dev);   //从设备树中获取GPIO数据
|-- devm_input_allocate_device(dev);    //分配input设备
|-- gpio_keys_open
|-- gpio_keys_report_state(ddata);
|-- gpio_keys_gpio_report_event(bdata);
|-- gpio_get_value_cansleep(button->gpio);//读键值
|-- exynos_ss_check_crash_key(button->code, state);
|-- input_event(input, type, button->code, button->value);//上报event
|-- gpio_keys_close
|-- gpio_keys_setup_key(pdev, input, bdata, button)//设置GPIO_key
|-- INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func)//工作队列
|-- gpio_keys_gpio_work_func
|-- gpio_keys_gpio_report_event(bdata);//event上报函数
|-- gpio_get_value_cansleep(button->gpio);//读键值
|-- exynos_ss_check_crash_key(button->code, state);
|-- input_event(input, type, button->code, button->value);//上报event事件
|-- sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);//在sys目录下创建属性文件
|-- input_register_device(input);//input设备注册
|-- device_init_wakeup(&pdev->dev, wakeup);//设备唤醒初始化
|-- device_set_wakeup_capable(dev, val);//设备能不能被唤醒
|-- device_set_wakeup_enable(dev, val);//设备使不使用唤醒


以上是probe回调函数的整个执行过程,下面来具体的分析一下上述的部分函数:

gpio_keys_setup_key(pdev, input, bdata, button);

static int gpio_keys_setup_key(struct platform_device *pdev,
struct input_dev *input,
struct gpio_button_data *bdata,
const struct gpio_keys_button *button)
{
const char *desc = button->desc ? button->desc : "gpio_keys";/*@desc:label that will be attached to button's gpio*/
struct device *dev = &pdev->dev;
irq_handler_t isr;
unsigned long irqflags;
int irq;
int error;

bdata->input = input;
bdata->button = button;
spin_lock_init(&bdata->lock);

if (gpio_is_valid(button->gpio)) {

error = devm_gpio_request_one(&pdev->dev, button->gpio,
GPIOF_IN, desc);
if (error < 0) {
dev_err(dev, "Failed to request GPIO %d, error %d\n",
button->gpio, error);
return error;
}

if (button->debounce_interval) {
error = gpio_set_debounce(button->gpio,
button->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (error < 0)
bdata->software_debounce =
button->debounce_interval;
}

if (button->irq) {
bdata->irq = button->irq;
} else {
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);
return error;
}
bdata->irq = irq;
}

INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);/*工作队列初始化,定义一个gpio_keys_gpio_work_func工作队列
函数去检测按键状态*/

isr = gpio_keys_gpio_isr;
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;

} else {
if (!button->irq) {
dev_err(dev, "No IRQ specified\n");
return -EINVAL;
}
bdata->irq = button->irq;

if (button->type && button->type != EV_KEY) {
dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");
return -EINVAL;
}

bdata->release_delay = button->debounce_interval;
setup_timer(&bdata->release_timer,
gpio_keys_irq_timer, (unsigned long)bdata);

isr = gpio_keys_irq_isr;
irqflags = 0;
}

input_set_capability(input, button->type ?: EV_KEY, button->code);

/*
* Install custom action to cancel release timer and
* workqueue item.
*/
error = devm_add_action(&pdev->dev, gpio_keys_quiesce_key, bdata);
if (error) {
dev_err(&pdev->dev,
"failed to register quiesce action, error: %d\n",
error);
return error;
}

/*
* 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;

if (button->wakeup)
irqflags |= IRQF_NO_SUSPEND;

error = devm_request_any_context_irq(&pdev->dev, bdata->irq,
isr, irqflags, desc, bdata);
if (error < 0) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
bdata->irq, error);
return error;
}

return 0;
}


gpio_keys_gpio_work_func函数定义:

static void gpio_keys_gpio_work_func(struct work_struct *work)
{
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work.work);

gpio_keys_gpio_report_event(bdata);

if (bdata->button->wakeup)
pm_relax(bdata->input->dev.parent);
}


gpio_keys_gpio_report_event(bdata)函数定义:

static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{
const 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);/*获取物理按键的状态值*/
struct irq_desc *desc = irq_to_desc(gpio_to_irq(button->gpio));

if (!desc) {
dev_err(input->dev.parent, "irq_desc is null! (gpio=%d)\n",
button->gpio);
return;
}

if (state < 0) {
dev_err(input->dev.parent, "failed to get gpio state\n");
return;
}

state = (state ? 1 : 0) ^ button->active_low;
exynos_ss_check_crash_key(button->code, state);/*该函数是烧录版本的时候按键的操作函数*/

if (type == EV_ABS) {
if (state)
input_event(input, type, button->code, button->value);//上报event事件
} else {
input_event(input, type, button->code,
irqd_is_wakeup_set(&desc->irq_data) ? 1 : !!state);
}
input_sync(input);
}


exynos_ss_check_crash_key(button->code, state)定义如下:

#ifdef CONFIG_EXYNOS_SNAPSHOT_CRASH_KEY
void exynos_ss_check_crash_key(unsigned int code, int value)
{
static bool volup_p;
static bool voldown_p;
static int loopcount;

static const unsigned int VOLUME_UP = KEY_VOLUMEUP;
static const unsigned int VOLUME_DOWN = KEY_VOLUMEDOWN;

/* Enter Forced Upload 进入版本下载操作如下:
*  Hold volume down key first
*  and then press power key twice
*  and volume up key should not be pressed
*/
if (value) {
if (code == VOLUME_UP)
volup_p = true;
if (code == VOLUME_DOWN)
voldown_p = true;
if (!volup_p && voldown_p) {
if (code == KEY_POWER) {
pr_info
("exynos-snapshot: count for entering forced upload [%d]\n",
++loopcount);
if (loopcount == 2) {
panic("Crash Key");
}
}
}
} else {
if (code == VOLUME_UP)
volup_p = false;
if (code == VOLUME_DOWN) {
loopcount = 0;
voldown_p = false;
}
}
}
#endif


三、手机的物理按键从原理图中可知,是直接接在CPU上的,底层驱动通过一个输入设备进行按键的检测,底层有个映射表(【samsung7872】【Z205】文件名:input-event-codes.h),为每个按键都分配了键值,当input设备检测到了按键按下,就会去该按键在映射表中相对应的键值,然后input子系统将获取到的键值上报给上层进行相应的处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: