// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

//#define DEBUG
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#ifdef CONFIG_AMLOGIC_GX_SUSPEND
#include <linux/amlogic/pm.h>
#endif
#include <linux/irq.h>
#include <linux/amlogic/power_domain.h>
#include <linux/amlogic/gpiolib.h>

#define DEFAULT_SCAN_PERION	20
#define	DEFAULT_POLL_MODE	0
#define KEY_JITTER_COUNT	1

struct pin_desc {
	int current_status;
	struct gpio_desc *desc;
	int irq_num;
	u32 code;
	u32 key_type;
	const char *name;
	int count;
};

struct gpio_keypad {
	int key_size;
	int use_irq;/* 1:irq mode ; 0:polling mode */
	int scan_period;
	struct pin_desc *key;
	struct pin_desc *current_key;
	struct timer_list polling_timer;
	struct input_dev *input_dev;
	struct class kp_class;
};

static irqreturn_t gpio_irq_handler(int irq, void *data)
{
	struct gpio_keypad *keypad;

	keypad = (struct gpio_keypad *)data;
	mod_timer(&keypad->polling_timer,
		  jiffies + msecs_to_jiffies(20));
	return IRQ_HANDLED;
}

static void report_key_code(struct gpio_keypad *keypad, int gpio_val)
{
	struct pin_desc *key = keypad->current_key;

	if (key->count >= KEY_JITTER_COUNT) {
		key->current_status = gpio_val;
		if (key->current_status) {
			input_event(keypad->input_dev, key->key_type,
				    key->code, 0);

			dev_dbg(&keypad->input_dev->dev,
				 "key %d up.\n", key->code);
		} else {
			input_event(keypad->input_dev, key->key_type,
				    key->code, 1);

			dev_dbg(&keypad->input_dev->dev,
				 "key %d down.\n", key->code);
		}
		input_sync(keypad->input_dev);

		key->count = 0;
	}
}

static void polling_timer_handler(struct timer_list *t)
{
	int i;
	int gpio_val;
	int is_pressing = 0;
	struct gpio_keypad *keypad = from_timer(keypad, t, polling_timer);

	if (keypad->use_irq) {/* irq mode */
		for (i = 0; i < keypad->key_size; i++) {
			gpio_val = gpiod_get_value(keypad->key[i].desc);
			if (gpio_val == 0) {
				keypad->key[i].count++;
				is_pressing = 1;
			}

			if (keypad->key[i].current_status != gpio_val) {
				keypad->current_key = &keypad->key[i];
				report_key_code(keypad, gpio_val);
			}
		}
		if (is_pressing)
			mod_timer(&keypad->polling_timer,
				  jiffies +
				  msecs_to_jiffies(keypad->scan_period));
	} else {/* polling mode */
		for (i = 0; i < keypad->key_size; i++) {
			gpio_val = gpiod_get_value(keypad->key[i].desc);
			if (gpio_val == 0)
				keypad->key[i].count++;
			if (keypad->key[i].current_status != gpio_val) {
				keypad->current_key = &keypad->key[i];
				report_key_code(keypad, gpio_val);
			}
		mod_timer(&keypad->polling_timer,
			  jiffies + msecs_to_jiffies(keypad->scan_period));
		}
	}
}

static ssize_t table_show(struct class *cls, struct class_attribute *attr,
			  char *buf)
{
	struct gpio_keypad *keypad = container_of(cls,
					struct gpio_keypad, kp_class);
	int i;
	int len = 0;

	for (i = 0; i < keypad->key_size; i++) {
		len += sprintf(buf + len,
			"[%d]: name = %-21s status = %-5d\n", i,
			keypad->key[i].name,
			keypad->key[i].current_status);
	}

	return len;
}

static CLASS_ATTR_RO(table);

static struct attribute *meson_gpiokey_attrs[] = {
	&class_attr_table.attr,
	NULL
};

ATTRIBUTE_GROUPS(meson_gpiokey);

static int meson_gpio_kp_probe(struct platform_device *pdev)
{
	struct gpio_desc *desc;
	int ret, i;
	struct input_dev *input_dev;
	struct gpio_keypad *keypad;
	struct irq_desc *d;
	unsigned int number;
	int wakeup_source = 0;
	int register_flag = 0;

	if (!(pdev->dev.of_node)) {
		dev_err(&pdev->dev,
			"pdev->dev.of_node == NULL!\n");
		return -EINVAL;
	}
	keypad = devm_kzalloc(&pdev->dev,
			      sizeof(struct gpio_keypad), GFP_KERNEL);
	if (!keypad)
		return -EINVAL;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "detect_mode", &keypad->use_irq);
	if (ret)
		/* The default mode is polling. */
		keypad->use_irq = DEFAULT_POLL_MODE;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "scan_period", &keypad->scan_period);
	if (ret)
		/* he default scan period is 20. */
		keypad->scan_period = DEFAULT_SCAN_PERION;
	if (of_property_read_bool(pdev->dev.of_node, "wakeup-source"))
		wakeup_source = 1;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "key_num", &keypad->key_size);
	if (ret) {
		dev_err(&pdev->dev,
			"failed to get key_num!\n");
		return -EINVAL;
	}
	keypad->key = devm_kzalloc(&pdev->dev,
				   (keypad->key_size) * sizeof(*keypad->key),
				   GFP_KERNEL);
	if (!(keypad->key))
		return -EINVAL;
	for (i = 0; i < keypad->key_size; i++) {
		/* get all gpio desc. */
		desc = devm_gpiod_get_index(&pdev->dev, "key", i, GPIOD_IN);
		if (IS_ERR_OR_NULL(desc))
			return -EINVAL;
		keypad->key[i].desc = desc;
		/* The gpio default is high level. */
		keypad->key[i].current_status = 1;
		ret = of_property_read_u32_index(pdev->dev.of_node,
						 "key_code", i,
						 &keypad->key[i].code);
		if (ret < 0) {
			dev_err(&pdev->dev,
				"find key_code=%d finished\n", i);
			return -EINVAL;
		}

		ret = of_property_read_u32_index(pdev->dev.of_node,
						 "key_type", i,
						 &keypad->key[i].key_type);
		if (ret)
			keypad->key[i].key_type = EV_KEY;

		ret = of_property_read_string_index(pdev->dev.of_node,
						    "key_name", i,
						    &keypad->key[i].name);
		if (ret < 0) {
			dev_err(&pdev->dev,
				"find key_name=%d finished\n", i);
			return -EINVAL;
		}
		gpiod_direction_input(keypad->key[i].desc);
		gpiod_set_pull(keypad->key[i].desc, GPIOD_PULL_UP);
	}

	keypad->kp_class.name = "gpio_keypad";
	keypad->kp_class.owner = THIS_MODULE;
	keypad->kp_class.class_groups = meson_gpiokey_groups;
	ret = class_register(&keypad->kp_class);
	if (ret) {
		dev_err(&pdev->dev, "fail to create gpio keypad class.\n");
		return -EINVAL;
	}

	/* input */
	input_dev = input_allocate_device();
	if (!input_dev)
		return -EINVAL;
	for (i = 0; i < keypad->key_size; i++) {
		input_set_capability(input_dev, keypad->key[i].key_type,
				     keypad->key[i].code);

		dev_dbg(&pdev->dev, "%s key(%d) type(0x%x) registered.\n",
			 keypad->key[i].name, keypad->key[i].code,
			 keypad->key[i].key_type);
	}
	input_dev->name = "gpio_keypad";
	input_dev->phys = "gpio_keypad/input0";
	input_dev->dev.parent = &pdev->dev;
	input_dev->id.bustype = BUS_ISA;
	input_dev->id.vendor = 0x0001;
	input_dev->id.product = 0x0001;
	input_dev->id.version = 0x0100;
	input_dev->rep[REP_DELAY] = 0xffffffff;
	input_dev->rep[REP_PERIOD] = 0xffffffff;
	input_dev->keycodesize = sizeof(unsigned short);
	input_dev->keycodemax = 0x1ff;
	keypad->input_dev = input_dev;
	ret = input_register_device(keypad->input_dev);
	if (ret < 0) {
		input_free_device(keypad->input_dev);
		return -EINVAL;
	}
	platform_set_drvdata(pdev, keypad);
	timer_setup(&keypad->polling_timer,
		    polling_timer_handler, 0);
	if (keypad->use_irq) {
		for (i = 0; i < keypad->key_size; i++) {
			keypad->key[i].count = 0;
			keypad->key[i].irq_num =
				gpiod_to_irq(keypad->key[i].desc);
			ret = devm_request_irq(&pdev->dev,
					       keypad->key[i].irq_num,
					       gpio_irq_handler,
					       IRQF_TRIGGER_FALLING,
					       "gpio_keypad", keypad);
			if (ret) {
				dev_err(&pdev->dev,
					"Requesting irq failed!\n");
				input_free_device(keypad->input_dev);
				del_timer(&keypad->polling_timer);
				return -EINVAL;
			}
			if (keypad->key[i].code == KEY_POWER) {
				d = irq_to_desc(keypad->key[i].irq_num);
				if (d) {
					number =
					 d->irq_data.parent_data->hwirq - 32;
					pwr_ctrl_irq_set(number, 1, 0);
					register_flag = 1;
				}
			}
		}
	} else {
		mod_timer(&keypad->polling_timer,
			  jiffies + msecs_to_jiffies(keypad->scan_period));
	}
	if (wakeup_source) {
		if (register_flag)
			dev_dbg(&pdev->dev,
				 "succeed to register wakeup source!\n");
		else
			dev_err(&pdev->dev,
				 "failed to register wakeup source!\n");
	}

	return 0;
}

static int meson_gpio_kp_remove(struct platform_device *pdev)
{
	struct gpio_keypad *keypad;

	keypad = platform_get_drvdata(pdev);
	class_unregister(&keypad->kp_class);
	input_unregister_device(keypad->input_dev);
	input_free_device(keypad->input_dev);
	del_timer(&keypad->polling_timer);
	return 0;
}

static const struct of_device_id key_dt_match[] = {
	{	.compatible = "amlogic, gpio_keypad", },
	{},
};

#ifdef CONFIG_AMLOGIC_GX_SUSPEND
static int meson_gpio_kp_suspend(struct platform_device *dev,
				 pm_message_t state)
{
	struct gpio_keypad *pdata;

	pdata = (struct gpio_keypad *)platform_get_drvdata(dev);
	if (!pdata->use_irq)
		del_timer(&pdata->polling_timer);
	return 0;
}

static int meson_gpio_kp_resume(struct platform_device *dev)
{
	int i;
	struct gpio_keypad *pdata;

	pdata = (struct gpio_keypad *)platform_get_drvdata(dev);
	if (!pdata->use_irq)
		mod_timer(&pdata->polling_timer,
			  jiffies + msecs_to_jiffies(5));
	if (get_resume_method() == POWER_KEY_WAKEUP) {
		for (i = 0; i < pdata->key_size; i++) {
			if (pdata->key[i].code == KEY_POWER) {
				dev_dbg(&dev->dev, "gpio keypad wakeup\n");
				input_report_key(pdata->input_dev,
						 KEY_POWER,  1);
				input_sync(pdata->input_dev);
				input_report_key(pdata->input_dev,
						 KEY_POWER,  0);
				input_sync(pdata->input_dev);
				break;
			}
		}
	}
	return 0;
}
#endif

static struct platform_driver meson_gpio_kp_driver = {
	.probe = meson_gpio_kp_probe,
	.remove = meson_gpio_kp_remove,
#ifdef CONFIG_AMLOGIC_GX_SUSPEND
	.suspend = meson_gpio_kp_suspend,
	.resume = meson_gpio_kp_resume,
#endif
	.driver = {
		.name = "gpio-keypad",
		.of_match_table = key_dt_match,
	},
};

int __init meson_gpio_kp_init(void)
{
	return platform_driver_register(&meson_gpio_kp_driver);
}

void __exit meson_gpio_kp_exit(void)
{
	platform_driver_unregister(&meson_gpio_kp_driver);
}
