/* arch/arm/mach-msm/smp2p_gpio.c
 *
 * Copyright (c) 2013, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/bitmap.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <mach/msm_ipc_logging.h>
#include "smp2p_private_api.h"
#include "smp2p_private.h"

/* GPIO device - one per SMP2P entry. */
struct smp2p_chip_dev {
	struct list_head entry_list;
	char name[SMP2P_MAX_ENTRY_NAME];
	int remote_pid;
	bool is_inbound;
	bool is_open;
	struct notifier_block out_notifier;
	struct notifier_block in_notifier;
	struct msm_smp2p_out *out_handle;

	struct gpio_chip gpio;
	struct irq_domain *irq_domain;
	int irq_base;

	spinlock_t irq_lock;
	DECLARE_BITMAP(irq_enabled, SMP2P_BITS_PER_ENTRY);
	DECLARE_BITMAP(irq_rising_edge, SMP2P_BITS_PER_ENTRY);
	DECLARE_BITMAP(irq_falling_edge, SMP2P_BITS_PER_ENTRY);
};

static struct platform_driver smp2p_gpio_driver;
static struct lock_class_key smp2p_gpio_lock_class;
static struct irq_chip smp2p_gpio_irq_chip;
static DEFINE_SPINLOCK(smp2p_entry_lock_lha1);
static LIST_HEAD(smp2p_entry_list);

/* Used for mapping edge to name for logging. */
static const char * const edge_names[] = {
	"-",
	"0->1",
	"1->0",
	"-",
};

/* Used for mapping edge to value for logging. */
static const char * const edge_name_rising[] = {
	"-",
	"0->1",
};

/* Used for mapping edge to value for logging. */
static const char * const edge_name_falling[] = {
	"-",
	"1->0",
};

static int smp2p_gpio_to_irq(struct gpio_chip *cp,
	unsigned offset);

/**
 * smp2p_get_value - Retrieves GPIO value.
 *
 * @cp:      GPIO chip pointer
 * @offset:  Pin offset
 * @returns: >=0: value of GPIO Pin; < 0 for error
 *
 * Error codes:
 *   -ENODEV - chip/entry invalid
 *   -ENETDOWN - valid entry, but entry not yet created
 */
static int smp2p_get_value(struct gpio_chip *cp,
	unsigned offset)
{
	struct smp2p_chip_dev *chip;
	int ret = 0;
	uint32_t data;

	if (!cp)
		return -ENODEV;

	chip = container_of(cp, struct smp2p_chip_dev, gpio);
	if (!chip->is_open)
		return -ENETDOWN;

	if (chip->is_inbound)
		ret = msm_smp2p_in_read(chip->remote_pid, chip->name, &data);
	else
		ret = msm_smp2p_out_read(chip->out_handle, &data);

	if (!ret)
		ret = (data & (1 << offset)) ? 1 : 0;

	return ret;
}

/**
 * smp2p_set_value - Sets GPIO value.
 *
 * @cp:     GPIO chip pointer
 * @offset: Pin offset
 * @value:  New value
 */
static void smp2p_set_value(struct gpio_chip *cp, unsigned offset, int value)
{
	struct smp2p_chip_dev *chip;
	uint32_t data_set;
	uint32_t data_clear;
	int ret;

	if (!cp)
		return;

	chip = container_of(cp, struct smp2p_chip_dev, gpio);
	if (!chip->is_open)
		return;

	if (chip->is_inbound) {
		SMP2P_ERR("%s: '%s':%d virq %d invalid operation\n",
			__func__, chip->name, chip->remote_pid,
			chip->irq_base + offset);
		return;
	}

	if (value) {
		data_set = 1 << offset;
		data_clear = 0;
	} else {
		data_set = 0;
		data_clear = 1 << offset;
	}

	ret = msm_smp2p_out_modify(chip->out_handle,
			data_set, data_clear);

	if (ret)
		SMP2P_GPIO("'%s':%d gpio %d set to %d failed (%d)\n",
			chip->name, chip->remote_pid,
			chip->gpio.base + offset, value, ret);
	else
		SMP2P_GPIO("'%s':%d gpio %d set to %d\n",
			chip->name, chip->remote_pid,
			chip->gpio.base + offset, value);
}

/**
 * smp2p_direction_input - Sets GPIO direction to input.
 *
 * @cp:      GPIO chip pointer
 * @offset:  Pin offset
 * @returns: 0 for success; < 0 for failure
 */
static int smp2p_direction_input(struct gpio_chip *cp, unsigned offset)
{
	struct smp2p_chip_dev *chip;

	if (!cp)
		return -ENODEV;

	chip = container_of(cp, struct smp2p_chip_dev, gpio);
	if (!chip->is_inbound)
		return -EPERM;

	return 0;
}

/**
 * smp2p_direction_output - Sets GPIO direction to output.
 *
 * @cp:      GPIO chip pointer
 * @offset:  Pin offset
 * @value:   Direction
 * @returns: 0 for success; < 0 for failure
 */
static int smp2p_direction_output(struct gpio_chip *cp,
	unsigned offset, int value)
{
	struct smp2p_chip_dev *chip;

	if (!cp)
		return -ENODEV;

	chip = container_of(cp, struct smp2p_chip_dev, gpio);
	if (chip->is_inbound)
		return -EPERM;

	return 0;
}

/**
 * smp2p_gpio_to_irq - Convert GPIO pin to virtual IRQ pin.
 *
 * @cp:      GPIO chip pointer
 * @offset:  Pin offset
 * @returns: >0 for virtual irq value; < 0 for failure
 */
static int smp2p_gpio_to_irq(struct gpio_chip *cp, unsigned offset)
{
	struct smp2p_chip_dev *chip;

	chip = container_of(cp, struct smp2p_chip_dev, gpio);
	if (!cp || chip->irq_base <= 0)
		return -ENODEV;

	return chip->irq_base + offset;
}

/**
 * smp2p_gpio_irq_mask_helper - Mask/Unmask interrupt.
 *
 * @d:    IRQ data
 * @mask: true to mask (disable), false to unmask (enable)
 */
static void smp2p_gpio_irq_mask_helper(struct irq_data *d, bool mask)
{
	struct smp2p_chip_dev *chip;
	int offset;
	unsigned long flags;

	chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
	if (!chip || chip->irq_base <= 0)
		return;

	offset = d->irq - chip->irq_base;
	spin_lock_irqsave(&chip->irq_lock, flags);
	if (mask)
		clear_bit(offset, chip->irq_enabled);
	else
		set_bit(offset, chip->irq_enabled);
	spin_unlock_irqrestore(&chip->irq_lock, flags);
}

/**
 * smp2p_gpio_irq_mask - Mask interrupt.
 *
 * @d: IRQ data
 */
static void smp2p_gpio_irq_mask(struct irq_data *d)
{
	smp2p_gpio_irq_mask_helper(d, true);
}

/**
 * smp2p_gpio_irq_unmask - Unmask interrupt.
 *
 * @d: IRQ data
 */
static void smp2p_gpio_irq_unmask(struct irq_data *d)
{
	smp2p_gpio_irq_mask_helper(d, false);
}

/**
 * smp2p_gpio_irq_set_type - Set interrupt edge type.
 *
 * @d:      IRQ data
 * @type:   Edge type for interrupt
 * @returns 0 for success; < 0 for failure
 */
static int smp2p_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
	struct smp2p_chip_dev *chip;
	int offset;
	unsigned long flags;
	int ret = 0;

	chip = (struct smp2p_chip_dev *)irq_get_chip_data(d->irq);
	if (!chip)
		return -ENODEV;

	if (chip->irq_base <= 0) {
		SMP2P_ERR("%s: '%s':%d virqbase %d invalid\n",
			__func__, chip->name, chip->remote_pid,
			chip->irq_base);
		return -ENODEV;
	}

	offset = d->irq - chip->irq_base;

	spin_lock_irqsave(&chip->irq_lock, flags);
	clear_bit(offset, chip->irq_rising_edge);
	clear_bit(offset, chip->irq_falling_edge);
	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		set_bit(offset, chip->irq_rising_edge);
		break;

	case IRQ_TYPE_EDGE_FALLING:
		set_bit(offset, chip->irq_falling_edge);
		break;

	case IRQ_TYPE_NONE:
	case IRQ_TYPE_DEFAULT:
	case IRQ_TYPE_EDGE_BOTH:
		set_bit(offset, chip->irq_rising_edge);
		set_bit(offset, chip->irq_falling_edge);
		break;

	default:
		SMP2P_ERR("%s: unsupported interrupt type 0x%x\n",
				__func__, type);
		ret = -EINVAL;
		break;
	}
	spin_unlock_irqrestore(&chip->irq_lock, flags);
	return ret;
}

/**
 * smp2p_irq_map - Creates or updates binding of virtual IRQ
 *
 * @domain_ptr: Interrupt domain pointer
 * @virq:       Virtual IRQ
 * @hw:         Hardware IRQ (same as virq for nomap)
 * @returns:    0 for success
 */
static int smp2p_irq_map(struct irq_domain *domain_ptr, unsigned int virq,
	irq_hw_number_t hw)
{
	struct smp2p_chip_dev *chip;

	chip = domain_ptr->host_data;
	if (!chip) {
		SMP2P_ERR("%s: invalid domain ptr %p\n", __func__, domain_ptr);
		return -ENODEV;
	}

	/* map chip structures to device */
	irq_set_lockdep_class(virq, &smp2p_gpio_lock_class);
	irq_set_chip_and_handler(virq, &smp2p_gpio_irq_chip,
				 handle_level_irq);
	irq_set_chip_data(virq, chip);
	set_irq_flags(virq, IRQF_VALID);

	return 0;
}

static struct irq_chip smp2p_gpio_irq_chip = {
	.name = "smp2p_gpio",
	.irq_mask = smp2p_gpio_irq_mask,
	.irq_unmask = smp2p_gpio_irq_unmask,
	.irq_set_type = smp2p_gpio_irq_set_type,
};

/* No-map interrupt Domain */
static const struct irq_domain_ops smp2p_irq_domain_ops = {
	.map = smp2p_irq_map,
};

/**
 * msm_summary_irq_handler - Handles inbound entry change notification.
 *
 * @chip:  GPIO chip pointer
 * @entry: Change notification data
 *
 * Whenever an entry changes, this callback is triggered to determine
 * which bits changed and if the corresponding interrupts need to be
 * triggered.
 */
static void msm_summary_irq_handler(struct smp2p_chip_dev *chip,
	struct msm_smp2p_update_notif *entry)
{
	int i;
	uint32_t cur_val;
	uint32_t prev_val;
	uint32_t edge;
	unsigned long flags;
	bool trigger_interrrupt;
	bool irq_rising;
	bool irq_falling;

	cur_val = entry->current_value;
	prev_val = entry->previous_value;

	if (chip->irq_base <= 0)
		return;

	SMP2P_GPIO("'%s':%d GPIO Summary IRQ Change %08x->%08x\n",
			chip->name, chip->remote_pid, prev_val, cur_val);

	for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) {
		spin_lock_irqsave(&chip->irq_lock, flags);
		trigger_interrrupt = false;
		edge = (prev_val & 0x1) << 1 | (cur_val & 0x1);
		irq_rising = test_bit(i, chip->irq_rising_edge);
		irq_falling = test_bit(i, chip->irq_falling_edge);

		if (test_bit(i, chip->irq_enabled)) {
			if (edge == 0x1 && irq_rising)
				/* 0->1 transition */
				trigger_interrrupt = true;
			else if (edge == 0x2 && irq_falling)
				/* 1->0 transition */
				trigger_interrrupt = true;
		} else {
			SMP2P_GPIO(
				"'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s disabled\n",
				chip->name, chip->remote_pid, i,
				chip->irq_base + i,
				edge_name_rising[irq_rising],
				edge_name_falling[irq_falling],
				edge_names[edge]);
		}
		spin_unlock_irqrestore(&chip->irq_lock, flags);

		if (trigger_interrrupt) {
			SMP2P_GPIO(
				"'%s':%d GPIO bit %d virq %d (%s,%s) - edge %s triggering\n",
				chip->name, chip->remote_pid, i,
				chip->irq_base + i,
				edge_name_rising[irq_rising],
				edge_name_falling[irq_falling],
				edge_names[edge]);
			(void)generic_handle_irq(chip->irq_base + i);
		}

		cur_val >>= 1;
		prev_val >>= 1;
	}
}

/**
 * Adds an interrupt domain based upon the DT node.
 *
 * @chip: pointer to GPIO chip
 * @node: pointer to Device Tree node
 */
static void smp2p_add_irq_domain(struct smp2p_chip_dev *chip,
	struct device_node *node)
{
	int i;

	/* map GPIO pins to interrupts */
	chip->irq_domain = irq_domain_add_nomap(node, 0,
			&smp2p_irq_domain_ops, chip);
	if (!chip->irq_domain) {
		SMP2P_ERR("%s: unable to create interrupt domain '%s':%d\n",
				__func__, chip->name, chip->remote_pid);
		return;
	}

	for (i = 0; i < SMP2P_BITS_PER_ENTRY; ++i) {
		unsigned int virt_irq;

		virt_irq = irq_create_direct_mapping(chip->irq_domain);
		if (virt_irq == NO_IRQ) {
			SMP2P_ERR("%s: gpio->virt IRQ mapping failed '%s':%d\n",
					__func__, chip->name, chip->remote_pid);
		} else if (!chip->irq_base) {
			chip->irq_base = virt_irq;
		}
	}
}

/**
 * Notifier function passed into smp2p API for out bound entries.
 *
 * @self:       Pointer to calling notifier block
 * @event:	    Event
 * @data:       Event-specific data
 * @returns:    0
 */
static int smp2p_gpio_out_notify(struct notifier_block *self,
		unsigned long event, void *data)
{
	struct smp2p_chip_dev *chip;

	chip = container_of(self, struct smp2p_chip_dev, out_notifier);

	switch (event) {
	case SMP2P_OPEN:
		chip->is_open = 1;
		SMP2P_GPIO("%s: Opened out '%s':%d\n", __func__,
				chip->name, chip->remote_pid);
		break;
	case SMP2P_ENTRY_UPDATE:
		break;
	default:
		SMP2P_ERR("%s: Unknown event\n", __func__);
		break;
	}
	return 0;
}

/**
 * Notifier function passed into smp2p API for in bound entries.
 *
 * @self:       Pointer to calling notifier block
 * @event:	    Event
 * @data:       Event-specific data
 * @returns:    0
 */
static int smp2p_gpio_in_notify(struct notifier_block *self,
		unsigned long event, void *data)
{
	struct smp2p_chip_dev *chip;

	chip = container_of(self, struct smp2p_chip_dev, in_notifier);

	switch (event) {
	case SMP2P_OPEN:
		chip->is_open = 1;
		SMP2P_GPIO("%s: Opened in '%s':%d\n", __func__,
				chip->name, chip->remote_pid);
		break;
	case SMP2P_ENTRY_UPDATE:
		msm_summary_irq_handler(chip, data);
		break;
	default:
		SMP2P_ERR("%s: Unknown event\n", __func__);
		break;
	}
	return 0;
}

/**
 * Device tree probe function.
 *
 * @pdev:	 Pointer to device tree data.
 * @returns: 0 on success; -ENODEV otherwise
 *
 * Called for each smp2pgpio entry in the device tree.
 */
static int __devinit smp2p_gpio_probe(struct platform_device *pdev)
{
	struct device_node *node;
	char *key;
	struct smp2p_chip_dev *chip;
	const char *name_tmp;
	unsigned long flags;
	bool is_test_entry = false;
	int ret;

	chip = kzalloc(sizeof(struct smp2p_chip_dev), GFP_KERNEL);
	if (!chip) {
		SMP2P_ERR("%s: out of memory\n", __func__);
		ret = -ENOMEM;
		goto fail;
	}
	spin_lock_init(&chip->irq_lock);

	/* parse device tree */
	node = pdev->dev.of_node;
	key = "qcom,entry-name";
	ret = of_property_read_string(node, key, &name_tmp);
	if (ret) {
		SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
		goto fail;
	}
	strlcpy(chip->name, name_tmp, sizeof(chip->name));

	key = "qcom,remote-pid";
	ret = of_property_read_u32(node, key, &chip->remote_pid);
	if (ret) {
		SMP2P_ERR("%s: missing DT key '%s'\n", __func__, key);
		goto fail;
	}

	key = "qcom,is-inbound";
	chip->is_inbound = of_property_read_bool(node, key);

	/* create virtual GPIO controller */
	chip->gpio.label = chip->name;
	chip->gpio.dev = &pdev->dev;
	chip->gpio.owner = THIS_MODULE;
	chip->gpio.direction_input	= smp2p_direction_input,
	chip->gpio.get = smp2p_get_value;
	chip->gpio.direction_output = smp2p_direction_output,
	chip->gpio.set = smp2p_set_value;
	chip->gpio.to_irq = smp2p_gpio_to_irq,
	chip->gpio.base = -1;	/* use dynamic GPIO pin allocation */
	chip->gpio.ngpio = SMP2P_BITS_PER_ENTRY;
	ret = gpiochip_add(&chip->gpio);
	if (ret) {
		SMP2P_ERR("%s: unable to register GPIO '%s' ret %d\n",
				__func__, chip->name, ret);
		goto fail;
	}

	/*
	 * Test entries opened by GPIO Test conflict with loopback
	 * support, so the test entries must be explicitly opened
	 * in the unit test framework.
	 */
	if (strncmp("smp2p", chip->name, SMP2P_MAX_ENTRY_NAME) == 0)
		is_test_entry = true;

	if (!chip->is_inbound)	{
		chip->out_notifier.notifier_call = smp2p_gpio_out_notify;
		if (!is_test_entry) {
			ret = msm_smp2p_out_open(chip->remote_pid, chip->name,
					   &chip->out_notifier,
					   &chip->out_handle);
			if (ret < 0)
				goto fail;
		}
	} else {
		chip->in_notifier.notifier_call = smp2p_gpio_in_notify;
		if (!is_test_entry) {
			ret = msm_smp2p_in_register(chip->remote_pid,
					chip->name,
					&chip->in_notifier);
			if (ret < 0)
				goto fail;
		}
	}

	spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
	list_add(&chip->entry_list, &smp2p_entry_list);
	spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);

	/*
	 * Create interrupt domain - note that chip can't be removed from the
	 * interrupt domain, so chip cannot be deleted after this point.
	 */
	if (chip->is_inbound)
		smp2p_add_irq_domain(chip, node);
	else
		chip->irq_base = -1;

	SMP2P_GPIO("%s: added %s%s entry '%s':%d gpio %d irq %d",
			__func__,
			is_test_entry ? "test " : "",
			chip->is_inbound ? "in" : "out",
			chip->name, chip->remote_pid,
			chip->gpio.base, chip->irq_base);

	return 0;

fail:
	kfree(chip);
	return ret;
}

/**
 * smp2p_gpio_open_close - Opens or closes entry.
 *
 * @entry:   Entry to open or close
 * @do_open: true = open port; false = close
 */
static void smp2p_gpio_open_close(struct smp2p_chip_dev *entry,
	bool do_open)
{
	int ret;

	if (do_open) {
		/* open entry */
		if (entry->is_inbound)
			ret = msm_smp2p_in_register(entry->remote_pid,
					entry->name, &entry->in_notifier);
		else
			ret = msm_smp2p_out_open(entry->remote_pid,
					entry->name, &entry->out_notifier,
					&entry->out_handle);
		SMP2P_GPIO("%s: opened %s '%s':%d ret %d\n",
				__func__,
				entry->is_inbound ? "in" : "out",
				entry->name, entry->remote_pid,
				ret);
	} else {
		/* close entry */
		if (entry->is_inbound)
			ret = msm_smp2p_in_unregister(entry->remote_pid,
					entry->name, &entry->in_notifier);
		else
			ret = msm_smp2p_out_close(&entry->out_handle);
		entry->is_open = false;
		SMP2P_GPIO("%s: closed %s '%s':%d ret %d\n",
				__func__,
				entry->is_inbound ? "in" : "out",
				entry->name, entry->remote_pid, ret);
	}
}

/**
 * smp2p_gpio_open_test_entry - Opens or closes test entries for unit testing.
 *
 * @name:       Name of the entry
 * @remote_pid: Remote processor ID
 * @do_open:    true = open port; false = close
 */
void smp2p_gpio_open_test_entry(const char *name, int remote_pid, bool do_open)
{
	struct smp2p_chip_dev *entry;
	struct smp2p_chip_dev *start_entry;
	unsigned long flags;

	spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
	if (list_empty(&smp2p_entry_list)) {
		spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
		return;
	}
	start_entry = list_first_entry(&smp2p_entry_list,
					struct smp2p_chip_dev,
					entry_list);
	entry = start_entry;
	do {
		if (!strncmp(entry->name, name, SMP2P_MAX_ENTRY_NAME)
				&& entry->remote_pid == remote_pid) {
			/* found entry to change */
			spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
			smp2p_gpio_open_close(entry, do_open);
			spin_lock_irqsave(&smp2p_entry_lock_lha1, flags);
		}
		list_rotate_left(&smp2p_entry_list);
		entry = list_first_entry(&smp2p_entry_list,
						struct smp2p_chip_dev,
						entry_list);
	} while (entry != start_entry);
	spin_unlock_irqrestore(&smp2p_entry_lock_lha1, flags);
}

static struct of_device_id msm_smp2p_match_table[] __devinitdata = {
	{.compatible = "qcom,smp2pgpio", },
	{},
};

static struct platform_driver smp2p_gpio_driver = {
	.probe = smp2p_gpio_probe,
	.driver = {
		.name = "smp2pgpio",
		.owner = THIS_MODULE,
		.of_match_table = msm_smp2p_match_table,
	},
};

static int __devinit smp2p_init(void)
{
	INIT_LIST_HEAD(&smp2p_entry_list);
	return platform_driver_register(&smp2p_gpio_driver);
}
module_init(smp2p_init);

static void __exit smp2p_exit(void)
{
	platform_driver_unregister(&smp2p_gpio_driver);
}
module_exit(smp2p_exit);

MODULE_DESCRIPTION("SMP2P GPIO");
MODULE_LICENSE("GPL v2");
