/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(C) 2021 Marvell.
 */

#ifndef _RTE_PMD_CNXK_GPIO_H_
#define _RTE_PMD_CNXK_GPIO_H_

#include <linux/gpio.h>
#include <linux/version.h>

#if KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE
#define CNXK_GPIO_V2_PRESENT
#else
struct gpio_v2_line_event {};
#endif

#include <rte_malloc.h>
#include <rte_memcpy.h>
#include <rte_rawdev.h>

/**
 * @file rte_pmd_cnxk_gpio.h
 *
 * Marvell GPIO PMD specific structures and interface
 *
 * This API allows applications to manage GPIOs in user space along with
 * installing interrupt handlers for low latency signal processing.
 */

#ifdef __cplusplus
extern "C" {
#endif

/* Queue default configuration */
struct cnxk_gpio_queue_conf {
	/** Queue size */
	int size;
	/** GPIO number as seen by hardware */
	int gpio;
};

/** Available message types */
enum cnxk_gpio_msg_type {
	/** Type used to set output value */
	CNXK_GPIO_MSG_TYPE_SET_PIN_VALUE,
	/** Type used to set edge */
	CNXK_GPIO_MSG_TYPE_SET_PIN_EDGE,
	/** Type used to set direction */
	CNXK_GPIO_MSG_TYPE_SET_PIN_DIR,
	/** Type used to set inverted logic */
	CNXK_GPIO_MSG_TYPE_SET_PIN_ACTIVE_LOW,
	/** Type used to read value */
	CNXK_GPIO_MSG_TYPE_GET_PIN_VALUE,
	/** Type used to read edge */
	CNXK_GPIO_MSG_TYPE_GET_PIN_EDGE,
	/** Type used to read direction */
	CNXK_GPIO_MSG_TYPE_GET_PIN_DIR,
	/** Type used to read inverted logic state */
	CNXK_GPIO_MSG_TYPE_GET_PIN_ACTIVE_LOW,
	/** Type used to register interrupt handler (deprecated) */
	CNXK_GPIO_MSG_TYPE_REGISTER_IRQ,
	/** Type used to register interrupt handler */
	CNXK_GPIO_MSG_TYPE_REGISTER_IRQ2,
	/** Type used to remove interrupt handler */
	CNXK_GPIO_MSG_TYPE_UNREGISTER_IRQ,
};

/** Available edges */
enum cnxk_gpio_pin_edge {
	/** Set edge to none */
	CNXK_GPIO_PIN_EDGE_NONE,
	/** Set edge to falling */
	CNXK_GPIO_PIN_EDGE_FALLING,
	/** Set edge to rising */
	CNXK_GPIO_PIN_EDGE_RISING,
	/** Set edge to both rising and falling */
	CNXK_GPIO_PIN_EDGE_BOTH,
};

/** Available directions */
enum cnxk_gpio_pin_dir {
	/** Set direction to input */
	CNXK_GPIO_PIN_DIR_IN,
	/** Set direction to output */
	CNXK_GPIO_PIN_DIR_OUT,
	/** Set direction to output and value to 1 */
	CNXK_GPIO_PIN_DIR_HIGH,
	/* Set direction to output and value to 0 */
	CNXK_GPIO_PIN_DIR_LOW,
};

/**
 * GPIO interrupt handler (deprecated)
 *
 * @param gpio
 *   Zero-based GPIO number
 * @param data
 *   Cookie passed to interrupt handler
 */
typedef void (*cnxk_gpio_irq_handler_t)(int gpio, void *data);

struct cnxk_gpio_irq {
	/** Interrupt handler */
	cnxk_gpio_irq_handler_t handler;
	/** User data passed to irq handler */
	void *data;
	/** CPU which will run irq handler */
	int cpu;
};

/**
 * GPIO interrupt handler
 *
 * @param event
 *   Pointer to gpio event data
 * @param data
 *   Cookie passed to interrupt handler
 */
typedef void (*cnxk_gpio_irq_handler2_t)(struct gpio_v2_line_event *event, void *data);

struct cnxk_gpio_irq2 {
	/** Interrupt handler */
	cnxk_gpio_irq_handler2_t handler;
	/** User data passed to irq handler */
	void *data;
};

struct cnxk_gpio_msg {
	/** Message type */
	enum cnxk_gpio_msg_type type;
	/** Message data passed to PMD or received from PMD */
	void *data;
};

/** @internal helper routine for enqueuing/dequeuing messages */
static __rte_always_inline int
__rte_pmd_gpio_enq_deq(uint16_t dev_id, int gpio, void *req, void *rsp,
		       size_t rsp_size)
{
	struct rte_rawdev_buf *bufs[1];
	struct rte_rawdev_buf buf;
	void *q;
	int ret;

	q = (void *)(size_t)gpio;
	buf.buf_addr = req;
	bufs[0] = &buf;

	ret = rte_rawdev_enqueue_buffers(dev_id, bufs, RTE_DIM(bufs), q);
	if (ret < 0)
		return ret;
	if (ret != RTE_DIM(bufs))
		return -EIO;

	if (!rsp)
		return 0;

	ret = rte_rawdev_dequeue_buffers(dev_id, bufs, RTE_DIM(bufs), q);
	if (ret < 0)
		return ret;
	if (ret != RTE_DIM(bufs))
		return -EIO;

	rte_memcpy(rsp, buf.buf_addr, rsp_size);
	rte_free(buf.buf_addr);

	return 0;
}

/**
 * Set output to specific value
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param val
 *   Value output will be set to. 0 represents low state while
 *   1 high state
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_set_pin_value(uint16_t dev_id, int gpio, int val)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_SET_PIN_VALUE,
		.data = &val,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, NULL, 0);
}

/**
 * Select signal edge that triggers interrupt
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param edge
 *   Signal edge that triggers interrupt
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_set_pin_edge(uint16_t dev_id, int gpio,
			  enum cnxk_gpio_pin_edge edge)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_SET_PIN_EDGE,
		.data = &edge
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, NULL, 0);
}

/**
 * Configure GPIO as input or output
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param dir
 *   Direction of the GPIO
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_set_pin_dir(uint16_t dev_id, int gpio, enum cnxk_gpio_pin_dir dir)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_SET_PIN_DIR,
		.data = &dir,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, NULL, 0);
}

/**
 * Enable or disable inverted logic
 *
 * If GPIO is configured as output then writing 1 or 0 will result in setting
 * output to respectively low or high
 *
 * If GPIO is configured as input then logic inversion applies to edges. Both
 * current and future settings are affected
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param val
 *   0 to disable, 1 to enable inverted logic
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_set_pin_active_low(uint16_t dev_id, int gpio, int val)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_SET_PIN_ACTIVE_LOW,
		.data = &val,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, NULL, 0);
}

/**
 * Read GPIO value
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param val
 *   Where to store read logical signal value i.e 0 or 1
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_get_pin_value(uint16_t dev_id, int gpio, int *val)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_GET_PIN_VALUE,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, val, sizeof(*val));
}

/**
 * Read GPIO edge
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param edge
 *   Where to store edge
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_get_pin_edge(uint16_t dev_id, int gpio,
			  enum cnxk_gpio_pin_edge *edge)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_GET_PIN_EDGE,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, edge, sizeof(*edge));
}

/**
 * Read GPIO direction
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param dir
 *   Where to store direction
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_get_pin_dir(uint16_t dev_id, int gpio, enum cnxk_gpio_pin_dir *dir)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_GET_PIN_DIR,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, dir, sizeof(*dir));
}

/**
 * Read whether GPIO is active low
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param val
 *   Where to store active low state
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_get_pin_active_low(uint16_t dev_id, int gpio, int *val)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_GET_PIN_ACTIVE_LOW,
		.data = &val,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, val, sizeof(*val));
}

/**
 * Attach interrupt handler to GPIO (deprecated)
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param cpu
 *   CPU which will be handling interrupt
 * @param handler
 *   Interrupt handler to be executed
 * @param data
 *   Data to be passed to interrupt handler
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_register_irq(uint16_t dev_id, int gpio, int cpu,
			  cnxk_gpio_irq_handler_t handler, void *data)
{
	struct cnxk_gpio_irq irq = {
		.handler = handler,
		.data = data,
		.cpu = cpu,
	};
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_REGISTER_IRQ,
		.data = &irq,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, NULL, 0);
}

/**
 * Attach interrupt handler to GPIO
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param handler
 *   Interrupt handler to be executed
 * @param data
 *   Data to be passed to interrupt handler
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_register_irq2(uint16_t dev_id, int gpio, cnxk_gpio_irq_handler2_t handler, void *data)
{
	struct cnxk_gpio_irq2 irq = {
		.handler = handler,
		.data = data,
	};
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_REGISTER_IRQ2,
		.data = &irq,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, NULL, 0);
}

/**
 * Detach interrupt handler from GPIO
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_unregister_irq(uint16_t dev_id, int gpio)
{
	struct cnxk_gpio_msg msg = {
		.type = CNXK_GPIO_MSG_TYPE_UNREGISTER_IRQ,
		.data = &gpio,
	};

	return __rte_pmd_gpio_enq_deq(dev_id, gpio, &msg, NULL, 0);
}

/**
 * Enable interrupt
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 * @param edge
 *   Edge that should trigger interrupt
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_enable_interrupt(uint16_t dev_id, int gpio,
			      enum cnxk_gpio_pin_edge edge)
{
	return rte_pmd_gpio_set_pin_edge(dev_id, gpio, edge);
}

/**
 * Disable interrupt
 *
 * @param dev_id
 *   The identifier of the device
 * @param gpio
 *   Zero-based GPIO number
 *
 * @return
 *   Returns 0 on success, negative error code otherwise
 */
static __rte_always_inline int
rte_pmd_gpio_disable_interrupt(uint16_t dev_id, int gpio)
{
	return rte_pmd_gpio_set_pin_edge(dev_id, gpio, CNXK_GPIO_PIN_EDGE_NONE);
}

#ifdef __cplusplus
}
#endif

#endif /* _RTE_PMD_CNXK_GPIO_H_ */
