diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/input/misc/da9063_onkey.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/drivers/input/misc/da9063_onkey.c b/drivers/input/misc/da9063_onkey.c new file mode 100644 index 0000000000..74808bae32 --- /dev/null +++ b/drivers/input/misc/da9063_onkey.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OnKey device driver for DA9063, DA9062 and DA9061 PMICs + * Copyright (C) 2015 Dialog Semiconductor Ltd. + */ + +#include <linux/devm-helpers.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> +#include <linux/workqueue.h> +#include <linux/regmap.h> +#include <linux/of.h> +#include <linux/mfd/da9063/core.h> +#include <linux/mfd/da9063/registers.h> +#include <linux/mfd/da9062/core.h> +#include <linux/mfd/da9062/registers.h> + +struct da906x_chip_config { + /* REGS */ + int onkey_status; + int onkey_pwr_signalling; + int onkey_fault_log; + int onkey_shutdown; + /* MASKS */ + int onkey_nonkey_mask; + int onkey_nonkey_lock_mask; + int onkey_key_reset_mask; + int onkey_shutdown_mask; + /* NAMES */ + const char *name; +}; + +struct da9063_onkey { + struct delayed_work work; + struct input_dev *input; + struct device *dev; + struct regmap *regmap; + const struct da906x_chip_config *config; + char phys[32]; + bool key_power; +}; + +static const struct da906x_chip_config da9063_regs = { + /* REGS */ + .onkey_status = DA9063_REG_STATUS_A, + .onkey_pwr_signalling = DA9063_REG_CONTROL_B, + .onkey_fault_log = DA9063_REG_FAULT_LOG, + .onkey_shutdown = DA9063_REG_CONTROL_F, + /* MASKS */ + .onkey_nonkey_mask = DA9063_NONKEY, + .onkey_nonkey_lock_mask = DA9063_NONKEY_LOCK, + .onkey_key_reset_mask = DA9063_KEY_RESET, + .onkey_shutdown_mask = DA9063_SHUTDOWN, + /* NAMES */ + .name = DA9063_DRVNAME_ONKEY, +}; + +static const struct da906x_chip_config da9062_regs = { + /* REGS */ + .onkey_status = DA9062AA_STATUS_A, + .onkey_pwr_signalling = DA9062AA_CONTROL_B, + .onkey_fault_log = DA9062AA_FAULT_LOG, + .onkey_shutdown = DA9062AA_CONTROL_F, + /* MASKS */ + .onkey_nonkey_mask = DA9062AA_NONKEY_MASK, + .onkey_nonkey_lock_mask = DA9062AA_NONKEY_LOCK_MASK, + .onkey_key_reset_mask = DA9062AA_KEY_RESET_MASK, + .onkey_shutdown_mask = DA9062AA_SHUTDOWN_MASK, + /* NAMES */ + .name = "da9062-onkey", +}; + +static const struct of_device_id da9063_compatible_reg_id_table[] = { + { .compatible = "dlg,da9063-onkey", .data = &da9063_regs }, + { .compatible = "dlg,da9062-onkey", .data = &da9062_regs }, + { }, +}; +MODULE_DEVICE_TABLE(of, da9063_compatible_reg_id_table); + +static void da9063_poll_on(struct work_struct *work) +{ + struct da9063_onkey *onkey = container_of(work, + struct da9063_onkey, + work.work); + const struct da906x_chip_config *config = onkey->config; + unsigned int val; + int fault_log = 0; + bool poll = true; + int error; + + /* Poll to see when the pin is released */ + error = regmap_read(onkey->regmap, + config->onkey_status, + &val); + if (error) { + dev_err(onkey->dev, + "Failed to read ON status: %d\n", error); + goto err_poll; + } + + if (!(val & config->onkey_nonkey_mask)) { + error = regmap_update_bits(onkey->regmap, + config->onkey_pwr_signalling, + config->onkey_nonkey_lock_mask, + 0); + if (error) { + dev_err(onkey->dev, + "Failed to reset the Key Delay %d\n", error); + goto err_poll; + } + + input_report_key(onkey->input, KEY_POWER, 0); + input_sync(onkey->input); + + poll = false; + } + + /* + * If the fault log KEY_RESET is detected, then clear it + * and shut down the system. + */ + error = regmap_read(onkey->regmap, + config->onkey_fault_log, + &fault_log); + if (error) { + dev_warn(&onkey->input->dev, + "Cannot read FAULT_LOG: %d\n", error); + } else if (fault_log & config->onkey_key_reset_mask) { + error = regmap_write(onkey->regmap, + config->onkey_fault_log, + config->onkey_key_reset_mask); + if (error) { + dev_warn(&onkey->input->dev, + "Cannot reset KEY_RESET fault log: %d\n", + error); + } else { + /* at this point we do any S/W housekeeping + * and then send shutdown command + */ + dev_dbg(&onkey->input->dev, + "Sending SHUTDOWN to PMIC ...\n"); + error = regmap_write(onkey->regmap, + config->onkey_shutdown, + config->onkey_shutdown_mask); + if (error) + dev_err(&onkey->input->dev, + "Cannot SHUTDOWN PMIC: %d\n", + error); + } + } + +err_poll: + if (poll) + schedule_delayed_work(&onkey->work, msecs_to_jiffies(50)); +} + +static irqreturn_t da9063_onkey_irq_handler(int irq, void *data) +{ + struct da9063_onkey *onkey = data; + const struct da906x_chip_config *config = onkey->config; + unsigned int val; + int error; + + error = regmap_read(onkey->regmap, + config->onkey_status, + &val); + if (onkey->key_power && !error && (val & config->onkey_nonkey_mask)) { + input_report_key(onkey->input, KEY_POWER, 1); + input_sync(onkey->input); + schedule_delayed_work(&onkey->work, 0); + dev_dbg(onkey->dev, "KEY_POWER long press.\n"); + } else { + input_report_key(onkey->input, KEY_POWER, 1); + input_sync(onkey->input); + input_report_key(onkey->input, KEY_POWER, 0); + input_sync(onkey->input); + dev_dbg(onkey->dev, "KEY_POWER short press.\n"); + } + + return IRQ_HANDLED; +} + +static int da9063_onkey_probe(struct platform_device *pdev) +{ + struct da9063_onkey *onkey; + const struct of_device_id *match; + int irq; + int error; + + match = of_match_node(da9063_compatible_reg_id_table, + pdev->dev.of_node); + if (!match) + return -ENXIO; + + onkey = devm_kzalloc(&pdev->dev, sizeof(struct da9063_onkey), + GFP_KERNEL); + if (!onkey) { + dev_err(&pdev->dev, "Failed to allocate memory.\n"); + return -ENOMEM; + } + + onkey->config = match->data; + onkey->dev = &pdev->dev; + + onkey->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!onkey->regmap) { + dev_err(&pdev->dev, "Parent regmap unavailable.\n"); + return -ENXIO; + } + + onkey->key_power = !of_property_read_bool(pdev->dev.of_node, + "dlg,disable-key-power"); + + onkey->input = devm_input_allocate_device(&pdev->dev); + if (!onkey->input) { + dev_err(&pdev->dev, "Failed to allocated input device.\n"); + return -ENOMEM; + } + + onkey->input->name = onkey->config->name; + snprintf(onkey->phys, sizeof(onkey->phys), "%s/input0", + onkey->config->name); + onkey->input->phys = onkey->phys; + onkey->input->dev.parent = &pdev->dev; + + input_set_capability(onkey->input, EV_KEY, KEY_POWER); + + error = devm_delayed_work_autocancel(&pdev->dev, &onkey->work, + da9063_poll_on); + if (error) { + dev_err(&pdev->dev, + "Failed to add cancel poll action: %d\n", + error); + return error; + } + + irq = platform_get_irq_byname(pdev, "ONKEY"); + if (irq < 0) + return irq; + + error = devm_request_threaded_irq(&pdev->dev, irq, + NULL, da9063_onkey_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "ONKEY", onkey); + if (error) { + dev_err(&pdev->dev, + "Failed to request IRQ %d: %d\n", irq, error); + return error; + } + + error = dev_pm_set_wake_irq(&pdev->dev, irq); + if (error) + dev_warn(&pdev->dev, + "Failed to set IRQ %d as a wake IRQ: %d\n", + irq, error); + else + device_init_wakeup(&pdev->dev, true); + + error = input_register_device(onkey->input); + if (error) { + dev_err(&pdev->dev, + "Failed to register input device: %d\n", error); + return error; + } + + return 0; +} + +static struct platform_driver da9063_onkey_driver = { + .probe = da9063_onkey_probe, + .driver = { + .name = DA9063_DRVNAME_ONKEY, + .of_match_table = da9063_compatible_reg_id_table, + }, +}; +module_platform_driver(da9063_onkey_driver); + +MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>"); +MODULE_DESCRIPTION("Onkey device driver for Dialog DA9063, DA9062 and DA9061"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DA9063_DRVNAME_ONKEY); |