高通平台 内核实现长按power键关机

本文介绍了如何在高通平台的内核中添加长按电源键关机的功能。通过修改设备树配置,增加特定属性,并在驱动代码中处理中断,使用延迟工作队列和信号量来判断和执行关机操作。当powerkey被长按时,会启动一个延迟2000ms的工作队列,如果在该时间内powerkey仍被按住,则执行关机命令。
摘要由CSDN通过智能技术生成

本文目的:总结高通平台内核中实现长按powerkey关机的功能

前期准备:

  1. 查看平台文档,确定powerkey的pmic设备树为pm.dtsi

  1. 根据compatible属性,定位到驱动文件qpnp-power-on.c

  1. 编写该功能的结构体

  1. 加入到驱动文件内合适的地方即可

由于我们是直接新增对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;
}

这里主要做了以下三个事情:

  1. 定义一个结构体,用于初始化一个bool类型变量、初始化一个信号量、初始化工作队列

  1. 工作队列的实现

  1. 判断设备树内是否打开这个功能

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;
}

针对驱动程序进行的修改,分以下几步:

  1. 增加qcom_parse_data函数(第一部分代码内的入口),为中断函数处理做好铺垫

  1. 在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的状态进行判断:

  1. 按下powerkey:此次中断是按下powerkey触发的,则通过延时队列schedule_delayed_work等待2000ms后执行qcom_power_work_func

  1. 松开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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值