| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/errno.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/workqueue.h> |
| #include <linux/regmap.h> |
| #include <linux/of.h> |
| #include <linux/timer.h> |
| |
| #include <linux/amlogic/pmic/meson_pmic6.h> |
| |
| /*short and long pwrkey time, uint is ms*/ |
| #define SHORT_PRESS_TIME 200 |
| #define LONG_PRESS_TIME 3000 |
| #define STEP_TIME 50 |
| #define SHORT_COUNT (SHORT_PRESS_TIME / STEP_TIME) |
| #define LONG_COUNT (LONG_PRESS_TIME / STEP_TIME) |
| |
| struct pmic6_pwrkey { |
| struct delayed_work work; |
| struct input_dev *input; |
| struct device *dev; |
| struct regmap *regmap; |
| struct timer_list timer; |
| unsigned int count; |
| }; |
| |
| static const struct of_device_id pmic6_pwrkey_id_table[] = { |
| { .compatible = "amlogic,pmic6-pwrkey",}, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, pmic6_pwrkey_id_table); |
| |
| static void pmic6_irq_work(struct work_struct *work) |
| { |
| struct pmic6_pwrkey *pwrkey = container_of(work, |
| struct pmic6_pwrkey, |
| work.work); |
| unsigned int val; |
| int ret; |
| |
| /* read pwrkey press status*/ |
| ret = regmap_read(pwrkey->regmap, |
| PMIC6_GEN_STATUS0, |
| &val); |
| if (ret) |
| dev_err(pwrkey->dev, |
| "Failed to read PWRKEY status: %d\n", ret); |
| |
| if ((val & 0x80)) { |
| pwrkey->count++; |
| if (pwrkey->count <= LONG_COUNT) |
| goto poll; |
| |
| if (pwrkey->count > LONG_COUNT) { |
| pwrkey->count = 0; |
| input_report_key(pwrkey->input, KEY_POWER, 0); |
| input_sync(pwrkey->input); |
| |
| ret = regmap_update_bits(pwrkey->regmap, |
| PMIC6_IRQ_MASK3, |
| BIT(6), BIT(6)); |
| if (ret) |
| dev_err(pwrkey->dev, "%d\n", __LINE__); |
| return; |
| } |
| |
| } else { |
| if (pwrkey->count > SHORT_COUNT) { |
| pwrkey->count = 0; |
| input_report_key(pwrkey->input, KEY_POWER, 1); |
| input_sync(pwrkey->input); |
| } |
| /*enable pwrkey irq*/ |
| ret = regmap_update_bits(pwrkey->regmap, |
| PMIC6_IRQ_MASK3, |
| BIT(6), BIT(6)); |
| if (ret) |
| dev_err(pwrkey->dev, "%d\n", __LINE__); |
| |
| return; |
| } |
| |
| poll: |
| schedule_delayed_work(&pwrkey->work, msecs_to_jiffies(STEP_TIME)); |
| } |
| |
| static irqreturn_t pmic6_pwrkey_irq_handler(int irq, void *data) |
| { |
| struct pmic6_pwrkey *pwrkey = data; |
| int ret; |
| |
| /*clear pwrkey irq*/ |
| ret = regmap_update_bits(pwrkey->regmap, |
| PMIC6_IRQ_MASK3, |
| BIT(6), 0); |
| if (ret) { |
| dev_err(pwrkey->dev, |
| "Failed to read PWRKEY status: %d\n", __LINE__); |
| return IRQ_HANDLED; |
| } |
| |
| schedule_delayed_work(&pwrkey->work, 0); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int pmic6_dev_of_pwrkey_init(struct pmic6_pwrkey *pwrkey) |
| { |
| int ret; |
| unsigned int val; |
| |
| /*short press time : 200ms, long : 3s*/ |
| val = (19 << 8) | (29 << 0); |
| ret = regmap_bulk_write(pwrkey->regmap, |
| PMIC6_PWR_KEY, |
| &val, 2); |
| if (ret) { |
| dev_err(pwrkey->dev, |
| "Failed to read PWRKEY status: %d\n", __LINE__); |
| return ret; |
| } |
| /*enable pwrkey irq*/ |
| ret = regmap_update_bits(pwrkey->regmap, |
| PMIC6_IRQ_MASK3, |
| BIT(6), BIT(6)); |
| if (ret) { |
| dev_err(pwrkey->dev, |
| "Failed to read PWRKEY status: %d\n", __LINE__); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int pmic6_pwrkey_probe(struct platform_device *pdev) |
| { |
| struct meson_pmic *pmic6 = dev_get_drvdata(pdev->dev.parent); |
| struct pmic6_pwrkey *pwrkey; |
| int irq; |
| int ret; |
| |
| pwrkey = devm_kzalloc(&pdev->dev, sizeof(struct pmic6_pwrkey), |
| GFP_KERNEL); |
| if (!pwrkey) { |
| dev_err(&pdev->dev, "Failed to allocated device.\n"); |
| return -ENOMEM; |
| } |
| |
| pwrkey->dev = &pdev->dev; |
| pwrkey->regmap = pmic6->regmap; |
| pwrkey->input = devm_input_allocate_device(&pdev->dev); |
| if (!pwrkey->input) { |
| dev_err(&pdev->dev, "Failed to allocated input device.\n"); |
| return -ENOMEM; |
| } |
| |
| pwrkey->input->name = "pmic6_pwrkey"; |
| pwrkey->input->phys = "pmic6_pwrkey/input0"; |
| pwrkey->input->dev.parent = &pdev->dev; |
| pwrkey->input->id.bustype = BUS_HOST; |
| input_set_capability(pwrkey->input, EV_KEY, KEY_POWER); |
| INIT_DELAYED_WORK(&pwrkey->work, pmic6_irq_work); |
| |
| irq = platform_get_irq_byname(pdev, "PWRKEY"); |
| if (irq < 0) { |
| ret = irq; |
| dev_err(&pdev->dev, "Failed to get platform IRQ: %d\n", ret); |
| return ret; |
| } |
| |
| ret = pmic6_dev_of_pwrkey_init(pwrkey); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "Failed to config pwrkey %d\n", ret); |
| return ret; |
| } |
| |
| ret = devm_request_threaded_irq(&pdev->dev, irq, |
| NULL, pmic6_pwrkey_irq_handler, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| "PWRKEY", pwrkey); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "Failed to request IRQ %d: %d\n", irq, ret); |
| return ret; |
| } |
| |
| ret = input_register_device(pwrkey->input); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "Failed to register input device: %d\n", ret); |
| return ret; |
| } |
| |
| platform_set_drvdata(pdev, pwrkey); |
| return 0; |
| } |
| |
| static struct platform_driver pmic6_pwrkey_driver = { |
| .probe = pmic6_pwrkey_probe, |
| .driver = { |
| .name = PMIC6_DRVNAME_ONKEY, |
| .of_match_table = pmic6_pwrkey_id_table, |
| }, |
| }; |
| module_platform_driver(pmic6_pwrkey_driver); |
| |
| MODULE_AUTHOR("Amlogic"); |
| MODULE_DESCRIPTION("PMIC6 input driver"); |
| MODULE_LICENSE("GPL v2"); |