本文目的:总结高通平台内核中实现长按powerkey关机的功能
前期准备:
查看平台文档,确定powerkey的pmic设备树为pm.dtsi
根据compatible属性,定位到驱动文件qpnp-power-on.c
编写该功能的结构体
加入到驱动文件内合适的地方即可
由于我们是直接新增对powerkey的长按定时处理,并且不希望通过input事件上报完成,因此修改代码应该在powerkey的中断处理函数中input事件上报之前即可。
下面是修改部分:
//pm.dtsi:
qcom,power-on@800 {
compatible = "qcom,qpnp-power-on";
reg = <0x800 0x100>;
interrupts = <0x0 0x8 0x0 IRQ_TYPE_NONE>,
<0x0 0x8 0x1 IRQ_TYPE_NONE>;
interrupt-names = "kpdpwr", "resin";
qcom,pon-dbc-delay = <15625>;
qcom,kpdpwr-sw-debounce;
qcom,system-reset;
qcom,store-hard-reset-reason;
//add
qcom,unused-pwrkey;
//end
...
};
这里通过在设备树内新增一个属性值,用来进行长按powerkey关机的功能开关,如果不需要这个功能的话只需要将这个属性删除即可。
/* Start add pwrkey power off */
struct qcom_power_data {
bool qcom_unused_pwrkey;
struct completion comp;
struct delayed_work work;
};
static struct qcom_power_data qcom_power;
static void qcom_power_work_func(struct work_struct *work)
{
wait_for_completion(&qcom_power.comp);
pr_err("[qcom_power] %s trigger power-off!\n", __func__);
machine_power_off();
BUG_ON(1);
}
static int qcom_parse_data(struct device_node *np)
{
//初始化一个中断延时工作队列
INIT_DELAYED_WORK(&qcom_power.work, qcom_power_work_func);
//初始化信号量
init_completion(&qcom_power.comp);
//of_property_read_bool用于判断设备树节点内是否存在这个属性,存在的话返回1,否则返回0
qcom_power.qcom_unused_pwrkey = of_property_read_bool(np, "qcom,unused-pwrkey");
pr_err("[qcom_power] qcom_unused_pwrkey=%d\n", qcom_power.qcom_unused_pwrkey);
return 0;
}
这里主要做了以下三个事情:
定义一个结构体,用于初始化一个bool类型变量、初始化一个信号量、初始化工作队列
工作队列的实现
判断设备树内是否打开这个功能
static int qpnp_pon_input_dispatch(struct qpnp_pon *pon, u32 pon_type)
{
...
pr_err("PMIC input: code=%d, status=0x%02X\n", cfg->key_code,
pon_rt_sts);
key_status = pon_rt_sts & pon_rt_bit;
pr_err("key_status = %d \n", key_status);
if (pon->kpdpwr_dbc_enable && cfg->pon_type == PON_KPDPWR) {
if (!key_status)
pon->kpdpwr_last_release_time = ktime_get();
if (qcom_power.qcom_unused_pwrkey) {
if (!key_status) {
if (!delayed_work_pending(&qcom_power.work)) {
complete(&qcom_power.comp);
}
cancel_delayed_work(&qcom_power.work);
} else {
schedule_delayed_work(&qcom_power.work, msecs_to_jiffies(2000)); // 2000 ms
}
return 0;
}
}
/*
* Simulate a press event in case release event occurred without a press
* event
*/
if (!cfg->old_state && !key_status) {
input_report_key(pon->pon_input, cfg->key_code, 1);
input_sync(pon->pon_input);
}
input_report_key(pon->pon_input, cfg->key_code, key_status);
input_sync(pon->pon_input);
cfg->old_state = !!key_status;
return 0;
}
static irqreturn_t qpnp_cblpwr_irq(int irq, void *_pon)
{
int rc;
struct qpnp_pon *pon = _pon;
rc = qpnp_pon_input_dispatch(pon, PON_CBLPWR);
if (rc)
dev_err(pon->dev, "Unable to send input event, rc=%d\n", rc);
return IRQ_HANDLED;
}
static int qpnp_pon_probe(struct platform_device *pdev)
{
...
if (sys_reset)
sys_reset_dev = pon;
qpnp_pon_debugfs_init(pon);
qcom_parse_data(dev->of_node);
return 0;
}
针对驱动程序进行的修改,分以下几步:
增加qcom_parse_data函数(第一部分代码内的入口),为中断函数处理做好铺垫
在powerkey中断函数qpnp_pon_input_dispatch进行input事件上报之前,增加判断逻辑,若是设备树内有打开这个功能,则进行如下判断:
2.1当前状态下powerkey未被按下,key_status为false,qcom_power.work无工作,则释放申请的信号量
2.2 当前状态下powerkey未被按下,key_status为false,qcom_power.work仍在等待中,代表此时未达到设定的时间,释放队列资源
2.3 当前状态下powerkey处于被按下的状态,则调用延时队列schedule_delayed_work。
这里可能有点绕,总结以下就是:
当powerkey被按下或者松开进入中断后,中断函数先判断是否支持长按powerkey关机功能,支持的话就会对此次powerkey的状态进行判断:
按下powerkey:此次中断是按下powerkey触发的,则通过延时队列schedule_delayed_work等待2000ms后执行qcom_power_work_func
松开powerkey:
2.1 松开powerkey前按下powerkey的时间未达到schedule_delayed_work内设定的时间,delayed_work_pending判断qcom_power.work尚未挂起,则取消这个延时任务。
2.2 松开powerkey前按下powerkey的时间达到schedule_delayed_work内设定的时间,则delayed_work_pending判断qcom_power.work已经挂起,在等待信号量的释放,那么就释放信号量,让他完成关机操作。
下面也顺势总结一下此次用到的各个工作队列及信号量的API
//判断一个工作队列是否已经挂起
#define delayed_work_pending(w) \
work_pending(&(w)->work)
//等待一定时间后将工作队列放到全局队列种
static inline bool schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay)
{
return queue_delayed_work(system_wq, dwork, delay);
}
//初始化延时工作队列
INIT_DELAYED_WORK
#define INIT_DELAYED_WORK(_work, _func) \
__INIT_DELAYED_WORK(_work, _func, 0)
//取消一个延时工作队列
bool cancel_delayed_work(struct delayed_work *dwork)
{
return __cancel_work(&dwork->work, true);
}
EXPORT_SYMBOL(cancel_delayed_work);
//初始化信号量
#define init_completion(x) __init_completion(x)
static inline void __init_completion(struct completion *x)
{
x->done = 0;
init_swait_queue_head(&x->wait);
}
//等待信号量释放
void __sched wait_for_completion(struct completion *x)
{
wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion);
//唤起某个线程
void complete(struct completion *x)
{
unsigned long flags;
raw_spin_lock_irqsave(&x->wait.lock, flags);
if (x->done != UINT_MAX)
x->done++;
swake_up_locked(&x->wait);
raw_spin_unlock_irqrestore(&x->wait.lock, flags);
}
EXPORT_SYMBOL(complete);