// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2017 Marvell
 *
 * Antoine Tenart <antoine.tenart@free-electrons.com>
 */

#include <linux/dma-mapping.h>
#include <linux/spinlock.h>

#include "safexcel.h"

int safexcel_init_ring_descriptors(struct safexcel_crypto_priv *priv,
				   struct safexcel_desc_ring *cdr,
				   struct safexcel_desc_ring *rdr)
{
	int i;
	struct safexcel_command_desc *cdesc;
	dma_addr_t atok;

	/* Actual command descriptor ring */
	cdr->offset = priv->config.cd_offset;
	cdr->base = dmam_alloc_coherent(priv->dev,
					cdr->offset * EIP197_DEFAULT_RING_SIZE,
					&cdr->base_dma, GFP_KERNEL);
	if (!cdr->base)
		return -ENOMEM;
	cdr->write = cdr->base;
	cdr->base_end = cdr->base + cdr->offset * (EIP197_DEFAULT_RING_SIZE - 1);
	cdr->read = cdr->base;

	/* Command descriptor shadow ring for storing additional token data */
	cdr->shoffset = priv->config.cdsh_offset;
	cdr->shbase = dmam_alloc_coherent(priv->dev,
					  cdr->shoffset *
					  EIP197_DEFAULT_RING_SIZE,
					  &cdr->shbase_dma, GFP_KERNEL);
	if (!cdr->shbase)
		return -ENOMEM;
	cdr->shwrite = cdr->shbase;
	cdr->shbase_end = cdr->shbase + cdr->shoffset *
					(EIP197_DEFAULT_RING_SIZE - 1);

	/*
	 * Populate command descriptors with physical pointers to shadow descs.
	 * Note that we only need to do this once if we don't overwrite them.
	 */
	cdesc = cdr->base;
	atok = cdr->shbase_dma;
	for (i = 0; i < EIP197_DEFAULT_RING_SIZE; i++) {
		cdesc->atok_lo = lower_32_bits(atok);
		cdesc->atok_hi = upper_32_bits(atok);
		cdesc = (void *)cdesc + cdr->offset;
		atok += cdr->shoffset;
	}

	rdr->offset = priv->config.rd_offset;
	/* Use shoffset for result token offset here */
	rdr->shoffset = priv->config.res_offset;
	rdr->base = dmam_alloc_coherent(priv->dev,
					rdr->offset * EIP197_DEFAULT_RING_SIZE,
					&rdr->base_dma, GFP_KERNEL);
	if (!rdr->base)
		return -ENOMEM;
	rdr->write = rdr->base;
	rdr->base_end = rdr->base + rdr->offset  * (EIP197_DEFAULT_RING_SIZE - 1);
	rdr->read = rdr->base;

	return 0;
}

inline int safexcel_select_ring(struct safexcel_crypto_priv *priv)
{
	return (atomic_inc_return(&priv->ring_used) % priv->config.rings);
}

static void *safexcel_ring_next_cwptr(struct safexcel_crypto_priv *priv,
				     struct safexcel_desc_ring *ring,
				     bool first,
				     struct safexcel_token **atoken)
{
	void *ptr = ring->write;

	if (first)
		*atoken = ring->shwrite;

	if ((ring->write == ring->read - ring->offset) ||
	    (ring->read == ring->base && ring->write == ring->base_end))
		return ERR_PTR(-ENOMEM);

	if (ring->write == ring->base_end) {
		ring->write = ring->base;
		ring->shwrite = ring->shbase;
	} else {
		ring->write += ring->offset;
		ring->shwrite += ring->shoffset;
	}

	return ptr;
}

static void *safexcel_ring_next_rwptr(struct safexcel_crypto_priv *priv,
				     struct safexcel_desc_ring *ring,
				     struct result_data_desc **rtoken)
{
	void *ptr = ring->write;

	/* Result token at relative offset shoffset */
	*rtoken = ring->write + ring->shoffset;

	if ((ring->write == ring->read - ring->offset) ||
	    (ring->read == ring->base && ring->write == ring->base_end))
		return ERR_PTR(-ENOMEM);

	if (ring->write == ring->base_end)
		ring->write = ring->base;
	else
		ring->write += ring->offset;

	return ptr;
}

void *safexcel_ring_next_rptr(struct safexcel_crypto_priv *priv,
			      struct safexcel_desc_ring *ring)
{
	void *ptr = ring->read;

	if (ring->write == ring->read)
		return ERR_PTR(-ENOENT);

	if (ring->read == ring->base_end)
		ring->read = ring->base;
	else
		ring->read += ring->offset;

	return ptr;
}

inline void *safexcel_ring_curr_rptr(struct safexcel_crypto_priv *priv,
				     int ring)
{
	struct safexcel_desc_ring *rdr = &priv->ring[ring].rdr;

	return rdr->read;
}

inline int safexcel_ring_first_rdr_index(struct safexcel_crypto_priv *priv,
					 int ring)
{
	struct safexcel_desc_ring *rdr = &priv->ring[ring].rdr;

	return (rdr->read - rdr->base) / rdr->offset;
}

inline int safexcel_ring_rdr_rdesc_index(struct safexcel_crypto_priv *priv,
					 int ring,
					 struct safexcel_result_desc *rdesc)
{
	struct safexcel_desc_ring *rdr = &priv->ring[ring].rdr;

	return ((void *)rdesc - rdr->base) / rdr->offset;
}

void safexcel_ring_rollback_wptr(struct safexcel_crypto_priv *priv,
				 struct safexcel_desc_ring *ring)
{
	if (ring->write == ring->read)
		return;

	if (ring->write == ring->base) {
		ring->write = ring->base_end;
		ring->shwrite = ring->shbase_end;
	} else {
		ring->write -= ring->offset;
		ring->shwrite -= ring->shoffset;
	}
}

struct safexcel_command_desc *safexcel_add_cdesc(struct safexcel_crypto_priv *priv,
						 int ring_id,
						 bool first, bool last,
						 dma_addr_t data, u32 data_len,
						 u32 full_data_len,
						 dma_addr_t context,
						 struct safexcel_token **atoken)
{
	struct safexcel_command_desc *cdesc;

	cdesc = safexcel_ring_next_cwptr(priv, &priv->ring[ring_id].cdr,
					 first, atoken);
	if (IS_ERR(cdesc))
		return cdesc;

	cdesc->particle_size = data_len;
	cdesc->rsvd0 = 0;
	cdesc->last_seg = last;
	cdesc->first_seg = first;
	cdesc->additional_cdata_size = 0;
	cdesc->rsvd1 = 0;
	cdesc->data_lo = lower_32_bits(data);
	cdesc->data_hi = upper_32_bits(data);

	if (first) {
		/*
		 * Note that the length here MUST be >0 or else the EIP(1)97
		 * may hang. Newer EIP197 firmware actually incorporates this
		 * fix already, but that doesn't help the EIP97 and we may
		 * also be running older firmware.
		 */
		cdesc->control_data.packet_length = full_data_len ?: 1;
		cdesc->control_data.options = EIP197_OPTION_MAGIC_VALUE |
					      EIP197_OPTION_64BIT_CTX |
					      EIP197_OPTION_CTX_CTRL_IN_CMD |
					      EIP197_OPTION_RC_AUTO;
		cdesc->control_data.type = EIP197_TYPE_BCLA;
		cdesc->control_data.context_lo = lower_32_bits(context) |
						 EIP197_CONTEXT_SMALL;
		cdesc->control_data.context_hi = upper_32_bits(context);
	}

	return cdesc;
}

struct safexcel_result_desc *safexcel_add_rdesc(struct safexcel_crypto_priv *priv,
						int ring_id,
						bool first, bool last,
						dma_addr_t data, u32 len)
{
	struct safexcel_result_desc *rdesc;
	struct result_data_desc *rtoken;

	rdesc = safexcel_ring_next_rwptr(priv, &priv->ring[ring_id].rdr,
					 &rtoken);
	if (IS_ERR(rdesc))
		return rdesc;

	rdesc->particle_size = len;
	rdesc->rsvd0 = 0;
	rdesc->descriptor_overflow = 1; /* assume error */
	rdesc->buffer_overflow = 1;     /* assume error */
	rdesc->last_seg = last;
	rdesc->first_seg = first;
	rdesc->result_size = EIP197_RD64_RESULT_SIZE;
	rdesc->rsvd1 = 0;
	rdesc->data_lo = lower_32_bits(data);
	rdesc->data_hi = upper_32_bits(data);

	/* Clear length in result token */
	rtoken->packet_length = 0;
	/* Assume errors - HW will clear if not the case */
	rtoken->error_code = 0x7fff;

	return rdesc;
}