#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/gpio/consumer.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#define DEVICE_NAME "hook"
#define COMPAT      "phone,hook"
#define BUTTON      BTN_0
#define PREFIX DEVICE_NAME ": "
#define DEBUG(...) if(debug) pr_info(PREFIX __VA_ARGS__);

MODULE_LICENSE      ("GPL");
MODULE_AUTHOR       ("Mochi the Dog");
MODULE_DESCRIPTION  ("A very trivial input device driver for a phone hook.");
MODULE_VERSION      ("1.0");

static int debug = 0;
module_param(debug, int, 0600);

static int              irq;
static struct gpio_desc *data_in;
static struct input_dev *input_dev;

static irqreturn_t irq_handler(int irq, void *dev) {
    int value = gpiod_get_value(data_in);
    DEBUG("Hook now %d\n", value);
    input_report_key(input_dev, BTN_0, value);
    input_sync(input_dev);
    return IRQ_HANDLED;
    }

static int hook_probe(struct platform_device *pdev) {
    int err;
    DEBUG("Probing " DEVICE_NAME ".");
    input_dev = input_allocate_device();
    if(IS_ERR(input_dev)) {
        pr_err(PREFIX "Can't allocate input device: %pe\n", input_dev);
        return -1;
        }
    input_dev->name = DEVICE_NAME;
    input_dev->evbit[0] |= BIT_MASK(EV_KEY);
    input_dev->keybit[BIT_WORD(BUTTON)] |= BIT_MASK(BUTTON);

    err = input_register_device(input_dev);
    if(err) {
        pr_err(PREFIX "Unable to register input device: %d\n", err);
        return -1;
        }
    data_in = gpiod_get(&pdev->dev, "din", GPIOD_IN);
    if(IS_ERR(data_in)) {
        pr_err(PREFIX "Unable to obtain GPIO data_in: %pe\n", data_in);
        return -1;
        }
    DEBUG("Using GPIO %d.\n", desc_to_gpio(data_in));
    irq = gpiod_to_irq(data_in);
    DEBUG("Assigned IRQ %d.\n", irq);
    err = request_irq(irq, (void*)irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DEVICE_NAME, (void*)pdev);
    if(err) {
        pr_err(PREFIX "Unable to request IRQ: %d\n", err);
        return err;
        }
    return 0;
    }

static int hook_remove(struct platform_device *pdev) {
    DEBUG("Removing device.\n");
    free_irq(irq, (void*)pdev);
    gpiod_put(data_in);
    input_unregister_device(input_dev);
    return 0;
    }

static const struct of_device_id of_hook_match[] = {
    { .compatible = COMPAT, },
    {},
    };
MODULE_DEVICE_TABLE(of, of_hook_match);

static struct platform_driver hook_driver = {
    .probe  = hook_probe,
    .remove = hook_remove,
    .driver = {
        .name = DEVICE_NAME,
        .of_match_table = of_hook_match,
        },
    };

module_platform_driver(hook_driver);
