/*
 * Greybus Simulator
 *
 * Copyright 2014-2016 Google Inc.
 * Copyright 2014-2016 Linaro Ltd.
 *
 * Provided under the three clause BSD license found in the LICENSE file.
 */

#include <fcntl.h>
#include <pthread.h>
#include <libsoc_gpio.h>
#include <linux/fs.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

#include "gbsim.h"

struct gb_gpio {
	uint8_t activated;
	uint8_t direction;
	uint8_t value;
	uint8_t irq_type;
	uint8_t irq_unmasked;
};

static struct gb_gpio gb_gpios[6];
static gpio *gpios[6];

static int gb_gpio_set_value(uint8_t which, uint8_t value)
{
	uint8_t which_con;

	gb_gpios[which].value = value;

	/*
	 * check to see if it is even, we do nothig. If it is odd, we simulate
	 * that the gpio is loopback to the even one before it. Of course, if
	 * activated and if it is set as input
	 */
	if (!(which % 2))
		return 0;

	which_con = which - 1;

	if (!gb_gpios[which_con].activated || gb_gpios[which_con].direction != 1)
		return 0;

	if (gb_gpios[which_con].value == value)
		return 0;

	gb_gpios[which_con].value = value;

	 /* check to see if we need to send an irq event */
	if (!gb_gpios[which_con].irq_type || !gb_gpios[which_con].irq_unmasked)
		return 0;

	if (gb_gpios[which_con].irq_type == GB_GPIO_IRQ_TYPE_EDGE_RISING &&
	    value)
		return 1;
	else if (gb_gpios[which_con].irq_type == GB_GPIO_IRQ_TYPE_EDGE_FALLING &&
		 !value)
		return 1;
	else if (gb_gpios[which_con].irq_type == GB_GPIO_IRQ_TYPE_EDGE_BOTH)
		return 1;

	return 0;
}

int gpio_handler(struct gbsim_connection *connection, void *rbuf,
		 size_t rsize, void *tbuf, size_t tsize)
{
	struct gb_operation_msg_hdr *oph;
	struct op_msg *op_req = rbuf;
	struct op_msg *op_rsp;
	size_t payload_size;
	ssize_t nbytes;
	uint16_t message_size;
	uint16_t hd_cport_id = connection->hd_cport_id;
	uint8_t which = 0;
	int send_event = 0;

	op_rsp = (struct op_msg *)tbuf;
	oph = (struct gb_operation_msg_hdr *)&op_req->header;

	switch (oph->type) {
	case GB_GPIO_TYPE_LINE_COUNT:
		payload_size = sizeof(struct gb_gpio_line_count_response);
		op_rsp->gpio_lc_rsp.count = 5; /* Something arbitrary, but useful */
		break;
	case GB_GPIO_TYPE_ACTIVATE:
		payload_size = 0;
		which = op_req->gpio_act_req.which;
		gbsim_debug("GPIO %d activate request\n", which);
		gb_gpios[which].activated = 1;
		break;
	case GB_GPIO_TYPE_DEACTIVATE:
		payload_size = 0;
		which = op_req->gpio_deact_req.which;
		gbsim_debug("GPIO %d deactivate request\n", which);
		gb_gpios[which].activated = 0;
		break;
	case GB_GPIO_TYPE_GET_DIRECTION:
		payload_size = sizeof(struct gb_gpio_get_direction_response);
		which = op_req->gpio_get_dir_req.which;
		if (bbb_backend)
			op_rsp->gpio_get_dir_rsp.direction = libsoc_gpio_get_direction(gpios[which]);
		else
			op_rsp->gpio_get_dir_rsp.direction = gb_gpios[which].direction;
		gbsim_debug("GPIO %d get direction (%d) response\n",
			    which, op_rsp->gpio_get_dir_rsp.direction);
		break;
	case GB_GPIO_TYPE_DIRECTION_IN:
		payload_size = 0;
		which = op_req->gpio_dir_input_req.which;
		gbsim_debug("GPIO %d direction input request\n", which);
		if (bbb_backend)
			libsoc_gpio_set_direction(gpios[which], INPUT);
		else
			gb_gpios[which].direction = 1;
		break;
	case GB_GPIO_TYPE_DIRECTION_OUT:
		payload_size = 0;
		which = op_req->gpio_dir_output_req.which;
		gbsim_debug("GPIO %d direction output request\n", which);
		if (bbb_backend)
			libsoc_gpio_set_direction(gpios[which], OUTPUT);
		else
			gb_gpios[which].direction = 0;
		break;
	case GB_GPIO_TYPE_GET_VALUE:
		payload_size = sizeof(struct gb_gpio_get_value_response);
		which = op_req->gpio_get_val_req.which;
		if (bbb_backend)
			op_rsp->gpio_get_val_rsp.value = libsoc_gpio_get_level(gpios[which]);
		else
			op_rsp->gpio_get_val_rsp.value = gb_gpios[which].value;
		gbsim_debug("GPIO %d get value (%d) response\n  ",
			    which, op_rsp->gpio_get_val_rsp.value);
		break;
	case GB_GPIO_TYPE_SET_VALUE:
		payload_size = 0;
		which = op_req->gpio_set_val_req.which;
		gbsim_debug("GPIO %d set value (%d) request\n  ",
			    which, op_req->gpio_set_val_req.value);
		if (bbb_backend)
			libsoc_gpio_set_level(gpios[which], op_req->gpio_set_val_req.value);
		else
			send_event = gb_gpio_set_value(which, op_req->gpio_set_val_req.value);
		break;
	case GB_GPIO_TYPE_SET_DEBOUNCE:
		payload_size = 0;
		gbsim_debug("GPIO %d set debounce (%d us) request\n  ",
			    op_req->gpio_set_db_req.which, op_req->gpio_set_db_req.usec);
		break;
	case GB_GPIO_TYPE_IRQ_TYPE:
		payload_size = 0;
		which = op_req->gpio_irq_type_req.which;
		gbsim_debug("GPIO %d set IRQ type %d request\n  ",
			    which, op_req->gpio_irq_type_req.type);
		gb_gpios[which].irq_type = op_req->gpio_irq_type_req.type;
		break;
	case GB_GPIO_TYPE_IRQ_MASK:
		payload_size = 0;
		which = op_req->gpio_irq_mask_req.which;
		gb_gpios[which].irq_unmasked = 0;
		break;
	case GB_GPIO_TYPE_IRQ_UNMASK:
		payload_size = 0;
		which = op_req->gpio_irq_unmask_req.which;
		gb_gpios[which].irq_unmasked = 1;
		break;
	case GB_REQUEST_TYPE_CPORT_SHUTDOWN:
		payload_size = 0;
		break;
	default:
		return -EINVAL;
	}

	message_size = sizeof(struct gb_operation_msg_hdr) + payload_size;
	nbytes = send_response(hd_cport_id, op_rsp, message_size,
				oph->operation_id, oph->type,
				PROTOCOL_STATUS_SUCCESS);
	if (nbytes)
		return nbytes;

#define TEST_HACK
#ifdef TEST_HACK
	/*
	 * Test GPIO interrupts by sending one when they become unmasked, or
	 * when set value trigger one
	 */
	if (send_event) {
		payload_size = sizeof(struct gb_gpio_irq_event_request);
		op_req->gpio_irq_event_req.which = which - 1;

		/* mask the irq to mimic fw action on event send */
		gb_gpios[which - 1].irq_unmasked = 0;
		message_size = sizeof(struct gb_operation_msg_hdr) + payload_size;
		return send_request(hd_cport_id, op_req, message_size, 0,
				    GB_GPIO_TYPE_IRQ_EVENT);
	}
#endif

	return 0;
}

char *gpio_get_operation(uint8_t type)
{
	switch (type) {
	case GB_REQUEST_TYPE_INVALID:
		return "GB_GPIO_TYPE_INVALID";
	case GB_REQUEST_TYPE_CPORT_SHUTDOWN:
		return "GB_REQUEST_TYPE_CPORT_SHUTDOWN";
	case GB_GPIO_TYPE_LINE_COUNT:
		return "GB_GPIO_TYPE_LINE_COUNT";
	case GB_GPIO_TYPE_ACTIVATE:
		return "GB_GPIO_TYPE_ACTIVATE";
	case GB_GPIO_TYPE_DEACTIVATE:
		return "GB_GPIO_TYPE_DEACTIVATE";
	case GB_GPIO_TYPE_GET_DIRECTION:
		return "GB_GPIO_TYPE_GET_DIRECTION";
	case GB_GPIO_TYPE_DIRECTION_IN:
		return "GB_GPIO_TYPE_DIRECTION_IN";
	case GB_GPIO_TYPE_DIRECTION_OUT:
		return "GB_GPIO_TYPE_DIRECTION_OUT";
	case GB_GPIO_TYPE_GET_VALUE:
		return "GB_GPIO_TYPE_GET_VALUE";
	case GB_GPIO_TYPE_SET_VALUE:
		return "GB_GPIO_TYPE_SET_VALUE";
	case GB_GPIO_TYPE_SET_DEBOUNCE:
		return "GB_GPIO_TYPE_SET_DEBOUNCE";
	case GB_GPIO_TYPE_IRQ_TYPE:
		return "GB_GPIO_TYPE_IRQ_TYPE";
	case GB_GPIO_TYPE_IRQ_MASK:
		return "GB_GPIO_TYPE_IRQ_MASK";
	case GB_GPIO_TYPE_IRQ_UNMASK:
		return "GB_GPIO_TYPE_IRQ_UNMASK";
	case GB_GPIO_TYPE_IRQ_EVENT:
		return "GB_GPIO_TYPE_IRQ_EVENT";
	default:
		return "(Unknown operation)";
	}
}

void gpio_init(void)
{
	int i;

	if (bbb_backend) {
		/*
		 * Grab the four onboard LEDs (gpio1:24-27) and then
		 * P9-12 and P8-26 (gpio1:28-29) to support input. The
		 * pins on the header can be used in loopback mode for
		 * testing.
		 */
		for (i=0; i<6; i++)
			gpios[i] = libsoc_gpio_request(56+i, LS_GREEDY);
	}
}