diff --git a/drivers/platform/msm/qpnp-power-on.c b/drivers/platform/msm/qpnp-power-on.c index 0d9c9233918d4e151dfcf3daf5ce7bb688de9045..03672e5ebf1cd4246208b907eadf9b19ca33b0b5 100644 --- a/drivers/platform/msm/qpnp-power-on.c +++ b/drivers/platform/msm/qpnp-power-on.c @@ -23,6 +23,10 @@ #include <linux/input.h> #include <linux/log2.h> #include <linux/qpnp/power-on.h> +#include <linux/timer.h> + +#include "../../staging/android/timed_output.h" +#include <linux/platform_data/msm_pwm_vibrator.h> /* Common PNP defines */ #define QPNP_PON_REVISION2(base) (base + 0x01) @@ -102,6 +106,11 @@ #define QPNP_PON_BUFFER_SIZE 9 +#define QPNP_LONGKEY_WARN_TIME_MAX 5000 /* 5 sec */ +#define QPNP_VIBE_TIME 2000 /* 2 sec */ +#define QPNP_VIBE_INIT_DELAY msecs_to_jiffies(2000) +#define QPNP_VIBE_INIT_RETRY 5 + enum pon_type { PON_KPDPWR, PON_RESIN, @@ -133,6 +142,13 @@ struct qpnp_pon { u16 base; struct delayed_work bark_work; u32 dbc; + struct delayed_work timed_work; + struct timed_output_dev *timed_dev; + struct hrtimer timed_timer; + int longkey_warn_time; + bool timed_inited; + bool vibed; + spinlock_t lock; }; static struct qpnp_pon *sys_reset_dev; @@ -450,6 +466,96 @@ qpnp_get_cfg(struct qpnp_pon *pon, u32 pon_type) return NULL; } +static void qpnp_pon_enable_vibrator(struct qpnp_pon *pon, int timeout) +{ + struct timed_output_dev *dev = pon->timed_dev; + unsigned long flags; + + if (dev && dev->enable) { + spin_lock_irqsave(&pon->lock, flags); + if (timeout || pon->vibed) + dev->enable(dev, timeout); + pon->vibed = !!timeout; + spin_unlock_irqrestore(&pon->lock, flags); + } +} + +static enum hrtimer_restart qpnp_pon_vibe_timer_func(struct hrtimer *timer) +{ + struct qpnp_pon *pon = + container_of(timer, struct qpnp_pon, timed_timer); + + qpnp_pon_enable_vibrator(pon, QPNP_VIBE_TIME); + + return HRTIMER_NORESTART; +} + +static void timed_work_func(struct work_struct *work) +{ + struct qpnp_pon *pon = + container_of(work, struct qpnp_pon, timed_work.work); + struct spmi_device *spmi; + struct device_node *dev_node; + struct device_node *node; + struct platform_device *pdev; + struct timed_vibrator_data *vib_data; + int rc; + static int retry = 0; + + if (!pon) { + pr_warn("%s: no device\n", __func__); + return; + } + + spmi = pon->spmi; + dev_node = spmi->dev.of_node; + + node = of_parse_phandle(dev_node, "qcom,external-vibrator", 0); + if (!node) { + dev_warn(&spmi->dev, + "Can't find qcom,external-vibrator phandle\n"); + return; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + dev_warn(&spmi->dev, + "Can't find the device by node\n"); + return; + } + + vib_data = platform_get_drvdata(pdev); + if (!vib_data) { + dev_warn(&spmi->dev, + "Can't find the vibrator data by pdev\n"); + + /* re-arm the work */ + if (retry < QPNP_VIBE_INIT_RETRY) { + retry++; + dev_warn(&spmi->dev, + "Re-armed timed init work\n"); + schedule_delayed_work(&pon->timed_work, + QPNP_VIBE_INIT_DELAY); + } + return; + } + + rc = of_property_read_u32(dev_node, "qcom,longkey-warn-time", + &pon->longkey_warn_time); + if (rc && rc != -EINVAL) { + dev_warn(&spmi->dev, + "Unable to read 'qcom,longkey-warn-time'\n"); + pon->longkey_warn_time = QPNP_LONGKEY_WARN_TIME_MAX; + } + + pon->timed_dev = &vib_data->dev; + + hrtimer_init(&pon->timed_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pon->timed_timer.function = qpnp_pon_vibe_timer_func; + + pon->timed_inited = true; +} + static int qpnp_pon_input_dispatch(struct qpnp_pon *pon, u32 pon_type) { @@ -508,6 +614,20 @@ qpnp_pon_input_dispatch(struct qpnp_pon *pon, u32 pon_type) cfg->old_state = !!key_status; + if (pon->timed_inited) { + if (key_status) { + hrtimer_start(&pon->timed_timer, + ns_to_ktime((u64)pon->longkey_warn_time * + NSEC_PER_MSEC), + HRTIMER_MODE_REL); + } else { + if (hrtimer_active(&pon->timed_timer)) + hrtimer_try_to_cancel(&pon->timed_timer); + + qpnp_pon_enable_vibrator(pon, 0); + } + } + return 0; } @@ -1355,6 +1475,9 @@ static int qpnp_pon_probe(struct spmi_device *spmi) dev_set_drvdata(&spmi->dev, pon); INIT_DELAYED_WORK(&pon->bark_work, bark_work_func); + INIT_DELAYED_WORK(&pon->timed_work, timed_work_func); + + spin_lock_init(&pon->lock); /* register the PON configurations */ rc = qpnp_pon_config_init(pon); @@ -1383,6 +1506,8 @@ static int qpnp_pon_probe(struct spmi_device *spmi) return rc; } + schedule_delayed_work(&pon->timed_work, 0); + return rc; }