/* Copyright (c) 2014 Samsung Electronics Co., Ltd */

#include <linux/gpio_debug.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/mutex.h>

struct gpio_debug_data;

struct gpio_debug_event {
	int                         gpio;
	int                         gpio_idx;
	struct gpio_debug_event_def def;
	struct gpio_debug_data      *data;
	struct dentry               *file;
};

struct gpio_debug_data {
	int                     gpio_count;
	int                     *gpios;
	struct dentry           *gpio_debug_dir;
	struct dentry           *gpio_debug_events_dir;
	struct platform_device  *pdev;
	struct gpio_debug_event *events;
	int                     event_count;
	int                     event_base;
};

static struct gpio_debug_data debug_data;

DEFINE_MUTEX(debug_lock);

enum {
	GPIO_DEBUG_TOGGLE_100,
	GPIO_DEBUG_TOGGLE_200,
};

static struct gpio_debug_event_def debug_events_table[] = {
	[GPIO_DEBUG_TOGGLE_100] = {
		.name = "toggle100",
		.description = "Toggle the GPIO 100 times at initialisation",
	},
	[GPIO_DEBUG_TOGGLE_200] = {
		.name = "toggle200",
		.description = "Toggle the GPIO 200 times at initialisation",
	},
};

static void gpio_debug_event(int gpio, int state)
{
	if (gpio >= 0)
		gpio_set_value(gpio, state);
}

static void gpio_debug_event_exec(int event_id, int state)
{
	if ((event_id >= 0) && (event_id < debug_data.event_count) && debug_data.events)
		gpio_debug_event(debug_data.events[event_id].gpio, state);
}

void gpio_debug_event_enter(int base, int id)
{
	gpio_debug_event_exec(base + id, 0);
}

void gpio_debug_event_exit(int base, int id)
{
	gpio_debug_event_exec(base + id, 1);
}

int gpio_debug_event_enabled(int base, int id)
{
	int event_id = base + id;

	if ((event_id >= 0) &&
	    (event_id < debug_data.event_count) &&
	    debug_data.events &&
	    debug_data.events[event_id].gpio >= 0)
		return 1;
	else
		return 0;
}

static int gpio_debug_event_link(struct gpio_debug_event *event, int gpio_index)
{
	struct gpio_debug_data *data = event->data;

	if (gpio_index >= data->gpio_count)
		return -ERANGE;

	if (gpio_index >= 0)
		event->gpio = data->gpios[gpio_index];
	else
		event->gpio = -1;

	event->gpio_idx = gpio_index;

	return 0;
}

static ssize_t event_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
{
	ssize_t                 ret = 0;
	struct gpio_debug_event *event = file->f_inode->i_private;
	char                    buf[256];
	int                     pos;

	mutex_lock(&debug_lock);

	pos = snprintf(buf, sizeof(buf), "Description:\n%s\n\nEvent is mapped to GPIO index %d with number %d\n", event->def.description, event->gpio_idx, event->gpio);
	ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos);

	mutex_unlock(&debug_lock);

	return ret;
}

static ssize_t event_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
	char                    *user_string;
	ssize_t                 ret;
	struct gpio_debug_event *event = file->f_inode->i_private;
	int                     new_index = -1;

	mutex_lock(&debug_lock);

	user_string = kmalloc(count + 1, GFP_KERNEL);
	memory_read_from_buffer(user_string, count, ppos, user_buf, count);

	user_string[count] = '\0';
	ret = (ssize_t)strnlen(user_string, count + 1);

	if (kstrtou32(user_string, 10, &new_index)) {
		return -EINVAL;

	gpio_debug_event_link(event, new_index);

	kfree(user_string);
	mutex_unlock(&debug_lock);

	return ret;
}

static const struct file_operations event_ops = {
	.read = event_read,
	.write = event_write,
};

static void create_event_file(struct gpio_debug_event *event)
{
	struct gpio_debug_data *data = event->data;

	if (data && data->gpio_debug_events_dir) {
		event->file = debugfs_create_file(event->def.name, 0660, data->gpio_debug_events_dir, event, &event_ops);
		if (IS_ERR_OR_NULL(event->file)) {
			event->file = NULL;
			pr_warn("%s: Could not create debugfs file for %s\n", __func__, event->def.name);
		}
	}
}

static void remove_event_file(struct gpio_debug_event *event)
{
	if (event && event->file) {
		debugfs_remove(event->file);
		event->file = NULL;
	}
}

static void gpio_debug_init_event(struct gpio_debug_data *data, struct gpio_debug_event_def *event, struct gpio_debug_event *event_save)
{
	event_save->def.description = event->description;
	event_save->def.name = event->name;
	event_save->gpio = -1;
	event_save->gpio_idx = -1;
	event_save->data = data;

	create_event_file(event_save);
}

static void gpio_debug_destroy_event(struct gpio_debug_event *event)
{
	remove_event_file(event);
	event->def.description = NULL;
	event->def.name = NULL;
	event->gpio = -1;
	event->gpio_idx = -1;
	event->data = NULL;
}

int gpio_debug_event_list_register(struct gpio_debug_event_def *events, int event_count)
{
	struct gpio_debug_data  *data = &debug_data;
	int                     start_index = data->event_count;
	struct gpio_debug_event *new_events;
	int                     new_event_count = data->event_count + event_count;
	int                     i, j;

	mutex_lock(&debug_lock);

	if (data->events)
		for (i = 0; i < data->event_count; i++)
			remove_event_file(&data->events[i]);

	new_events = krealloc(data->events, new_event_count * sizeof(struct gpio_debug_event), GFP_KERNEL);
	if (!new_events) {
		pr_warn("%s: Could not expand for extra events\n", __func__);
		/* If krealloc fails, data->events is unchanged, so just exit */
		return -ENOMEM;
	}
	data->events = new_events;
	for (i = 0; i < data->event_count; i++)
		create_event_file(&data->events[i]);

	data->event_count = new_event_count;

	for (i = 0, j = start_index; (i < event_count) && (j < data->event_count); i++, j++)
		gpio_debug_init_event(data, &events[i], &data->events[j]);

	mutex_unlock(&debug_lock);
	return start_index;
}

void gpio_debug_event_list_unregister(int base, int event_count)
{
	int                    i;
	struct gpio_debug_data *data = &debug_data;

	mutex_lock(&debug_lock);

	for (i = base; (i < (event_count + base)) && (i < data->event_count); i++)
		gpio_debug_destroy_event(&data->events[i]);

	mutex_unlock(&debug_lock);
}

static ssize_t event_list_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
{
	int                    i;
	ssize_t                ret = 0;
	int                    length = 0;
	char                   *buf;
	struct gpio_debug_data *data = file->f_inode->i_private;
	struct device          *dev = &data->pdev->dev;
	char                   headline[] = " gpio event\n";

	mutex_lock(&debug_lock);

	length += strlen(headline);

	for (i = 0; i < data->event_count; i++)
		if (data->events[i].def.name)
			length += strlen(data->events[i].def.name) + 7;
	length++; /* Reserve space for NULL termination */

	buf = devm_kzalloc(dev, length, GFP_KERNEL);
	buf[0] = '\0';
	snprintf(buf, length, "%s", headline);
	for (i = 0; i < data->event_count; i++)
		if (data->events[i].data) {
			if (data->events[i].gpio_idx >= 0)
				snprintf(buf, length, "%s%5d %s\n", buf, data->events[i].gpio_idx, data->events[i].def.name);
			else
				snprintf(buf, length, "%s      %s\n", buf, data->events[i].def.name);
		}
	ret = simple_read_from_buffer(user_buf, count, ppos, buf, length);
	devm_kfree(dev, buf);

	mutex_unlock(&debug_lock);

	return ret;
}

static const struct file_operations event_list_ops = {
	.read = event_list_read,
};

static ssize_t num_gpios_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
{
	ssize_t                ret = 0;
	struct gpio_debug_data *data = file->f_inode->i_private;
	char                   buf[256];
	int                    pos;

	pos = snprintf(buf, sizeof(buf), "%d\n", data->gpio_count);
	ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos);
	return ret;
}

static const struct file_operations num_gpios_ops = {
	.read = num_gpios_read,
};

static int gpio_debug_probe(struct platform_device *pdev)
{
	struct device_node     *np = pdev->dev.of_node;
	struct device          *dev = &pdev->dev;
	int                    count;
	struct gpio_debug_data *data = &debug_data;
	int                    i, j;

	mutex_lock(&debug_lock);

	count = of_gpio_count(np);
	if (count < 0)
		count = 0;  /* Errors register as no GPIOs available */

	data->gpio_count = count;
	data->gpios = NULL;
	data->pdev = pdev;

	if (count) {
		data->gpios = devm_kzalloc(dev, count * sizeof(int), GFP_KERNEL);
		for (i = 0; i < count; i++) {
			data->gpios[i] = of_get_gpio(np, i);
			dev_info(dev, "GPIO at index %d has number %d\n", i, data->gpios[i]);
			if (gpio_is_valid(data->gpios[i])) {
				char label[256];

				sprintf(label, "debug-gpio-%d", i);
				dev_info(dev, "Requesting GPIO %d index %d with label %s\n", data->gpios[i], i, label);
				if (devm_gpio_request(dev, data->gpios[i], label))
					dev_err(dev, "GPIO [%d] request failed\n", data->gpios[i]);
				gpio_set_value(data->gpios[i], 1);
			} else
				dev_warn(dev, "GPIO at index %d is invalid\n", i);
		}
	}

	data->gpio_debug_dir = debugfs_create_dir("gpio_debug", NULL);
	if (!IS_ERR_OR_NULL(data->gpio_debug_dir)) {
		data->gpio_debug_events_dir = debugfs_create_dir("events", data->gpio_debug_dir);
		if (IS_ERR_OR_NULL(data->gpio_debug_events_dir)) {
			data->gpio_debug_events_dir = NULL;
			dev_err(dev, "Debugfs cannot create subdir\n");
		}
		debugfs_create_file("event_list", 0440, data->gpio_debug_dir, data, &event_list_ops);
		debugfs_create_file("num_gpios", 0440, data->gpio_debug_dir, data, &num_gpios_ops);
	} else {
		data->gpio_debug_dir = NULL;
		dev_warn(dev, "Debugfs is not available, configuration of GPIO debug is not possible\n");
	}

	for (i = 0; i < data->event_count; i++)
		create_event_file(&data->events[i]);

	mutex_unlock(&debug_lock);

	data->event_base = gpio_debug_event_list_register(debug_events_table, ARRAY_SIZE(debug_events_table));

	for (i = 0; i < count; i++) {
		gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_100], i);
		for (j = 0; j < 100; j++) {
			gpio_debug_event_enter(data->event_base, GPIO_DEBUG_TOGGLE_100);
			gpio_debug_event_exit(data->event_base, GPIO_DEBUG_TOGGLE_100);
		}
	}
	gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_100], -1);

	for (i = 0; i < count; i++) {
		gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_200], i);
		for (j = 0; j < 200; j++) {
			gpio_debug_event_enter(data->event_base, GPIO_DEBUG_TOGGLE_200);
			gpio_debug_event_exit(data->event_base, GPIO_DEBUG_TOGGLE_200);
		}
	}
	gpio_debug_event_link(&data->events[data->event_base + GPIO_DEBUG_TOGGLE_200], -1);

	return 0;
}

static int gpio_debug_remove(struct platform_device *pdev)
{
	struct device          *dev = &pdev->dev;
	struct gpio_debug_data *data = &debug_data;

	mutex_lock(&debug_lock);
	debugfs_remove_recursive(data->gpio_debug_dir);
	data->gpio_debug_dir = NULL;
	data->gpio_debug_events_dir = NULL;

	if (data->gpios) {
		int i;

		for (i = 0; i < data->gpio_count; i++)
			if (gpio_is_valid(data->gpios[i]))
				devm_gpio_free(dev, data->gpios[i]);
		devm_kfree(dev, data->gpios);
		data->gpios = NULL;
		data->gpio_count = 0;
	}
	data->pdev = NULL;
	kfree(data->events);
	data->events = NULL;
	data->event_count = 0;
	mutex_unlock(&debug_lock);

	return 0;
}

static const struct of_device_id gpio_debug_match[] = {
	{ .compatible = "samsung,gpio-debug", },
	{},
};
MODULE_DEVICE_TABLE(of, gpio_debug_match);

static struct platform_driver    gpio_debug_driver = {
	.probe          = gpio_debug_probe,
	.remove         = gpio_debug_remove,
	.driver         = {
		.name           = "gpio_debug",
		.of_match_table = gpio_debug_match,
	}
};
module_platform_driver(gpio_debug_driver);

MODULE_DESCRIPTION("GPIO Debug framework");
MODULE_AUTHOR("Samsung Electronics Co., Ltd");
MODULE_LICENSE("GPL and additional rights");